Next.js SSG静态站点累积布局偏移(CLS)问题排查:静态HTML内容缺失原因咨询
Next.js SSG静态生成不完整导致累积布局偏移(CLS)问题排查
问题描述
我用Next.js的静态生成(SSG)方式构建落地页,但遇到了累积布局偏移(CLS)的问题。打开生成的index.html后发现,并非所有HTML内容都已提前生成——页面加载时Next.js会动态注入部分缺失内容。按我的理解,SSG应该输出完整的静态HTML文件,所以想请教:我在SSG的理解或配置上哪里出了问题?
以下是我的页面数据获取代码和首页组件代码:
页面数据获取代码
import Head from "next/head" import { Box, Center, Container, Heading, Tag, } from "@chakra-ui/react"; import { request } from "/lib/datocms"; import { MAIN_MENU_QUERY, BLOG_POSTS_QUERY, BLOG_CATEGORIES_QUERY, HOME_PAGE_QUERY, } from "/lib/queries"; import BlockRender from "/components/blocks/BlockRender"; import ChakraNextLink from "/components/atoms/ChakraNextLink"; import BlogGrid from "/components/blog/BlogGrid" import { renderMetaTags } from "react-datocms"; ... export const getStaticProps = async ({ preview }) => { const graphqlRequest = { query: MAIN_MENU_QUERY, preview } const data = await request(graphqlRequest); const homePageGraphqlRequest = { query: HOME_PAGE_QUERY, preview } const homePage = await request(homePageGraphqlRequest); const pageData = homePage?.homePage; const blogPostsGraphqlRequest = { query: BLOG_POSTS_QUERY, preview, variables: { count: homePage.homePage.blogPostCount ?? 0 } } const blogPosts = await request(blogPostsGraphqlRequest); const blogCategoriesGraphqlRequest = { query: BLOG_CATEGORIES_QUERY, preview } const blogCategories = await request(blogCategoriesGraphqlRequest); return { props: { data, pageData, blogPosts, blogCategories }, }; };
首页组件代码
export default function Home({ data, pageData, blogPosts, blogCategories }) { const metaTags = pageData.seo.concat(data.site.favicon); return ( <> <Head> {renderMetaTags(metaTags)} </Head> {/* 以下内容未出现在生成的Index.html文件中,导致CLS问题 */} {pageData.content && pageData.content.map((block, key) => <BlockRender key={key} block={block} />)} {/* 以下内容存在于生成的index.html文件中 */} <Box w="full" py="12"> {pageData.showBlogSection && ( <BlogSection posts={blogPosts} categories={blogCategories}> )} </Box> </> ) }
问题排查与解决方案
1. 核心原因:BlockRender组件的客户端依赖
最可能的问题出在BlockRender组件本身——如果这个组件内部使用了仅能在浏览器环境运行的API(比如window、document),或者依赖useEffect/useLayoutEffect这类客户端生命周期钩子来渲染内容,Next.js在SSG阶段(服务器端)是不会执行这些逻辑的,所以这部分内容不会被编译进静态HTML,只能在客户端注水时动态渲染,进而导致CLS。
解决办法:
- 检查
BlockRender的代码,移除或替换浏览器专属API:比如把window.innerWidth这类逻辑换成Next.js兼容的方案,或者在组件挂载后再执行这类逻辑(同时给内容设置固定占位尺寸,避免布局偏移)。 - 如果组件确实需要完全在客户端渲染(比如依赖第三方交互库),可以用Next.js的
dynamic导入并禁用SSR,但要给这部分内容设置固定的宽高占位:import dynamic from 'next/dynamic'; const BlockRender = dynamic(() => import('/components/blocks/BlockRender'), { ssr: false, loading: () => <Box width="100%" height="400px" bg="gray.100" />, // 占位容器 });
2. 验证getStaticProps的数据完整性
确认pageData.content在SSG阶段是否正确获取到了数据:
- 在
getStaticProps中添加日志输出,运行next build后查看终端:export const getStaticProps = async ({ preview }) => { // ... 现有代码 console.log('pageData content:', pageData.content); // 检查数据是否存在 return { props: { data, pageData, blogPosts, blogCategories }, }; }; - 如果日志显示
pageData.content为空或未定义,那问题出在DatoCMS的查询上——检查HOME_PAGE_QUERY是否正确请求了content字段,或者预览模式下的数据是否有差异。
3. Chakra UI的静态渲染兼容性
Chakra UI的大部分组件支持SSG,但少数组件可能依赖客户端上下文(比如useColorMode)。如果BlockRender里用到了这类组件:
- 确保在根组件(比如
_app.js)中正确包裹ChakraProvider,并预生成主题样式。 - 尝试在
_document.js中提取静态样式,避免客户端加载时样式闪变:import { ColorModeScript } from '@chakra-ui/react'; import Document, { Html, Head, Main, NextScript } from 'next/document'; import theme from '../theme'; class MyDocument extends Document { render() { return ( <Html lang="en"> <Head /> <body> <ColorModeScript initialColorMode={theme.config.initialColorMode} /> <Main /> <NextScript /> </body> </Html> ); } } export default MyDocument;
4. 额外的CLS优化
即使解决了静态生成问题,还可以通过以下方式进一步降低CLS:
- 所有图片使用Next.js的
Image组件,它会自动生成占位并设置宽高比,避免图片加载时的布局偏移。 - 在
Head中添加字体预加载标签,减少字体加载时的布局变化:<link rel="preload" href="/fonts/your-font.woff2" as="font" type="font/woff2" crossorigin> - 避免动态注入内联样式,确保所有样式在静态HTML中提前加载。
内容的提问来源于stack exchange,提问作者Primoz Rome
相关产品推荐
相关产品推荐

