You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 20:44:09