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

Next.js SSR/SSG模式下如何输出纯HTML以优化SEO?

Next.js SSR/SSG 模式下输出纯 HTML 源码的可行方案

问题描述

我正在使用 ^12.2.3 的 Next.js、^18.2.0 的 React 和 ^4.7.4 的 TypeScript 开发 Web 应用。采用 Next.js 的 SSR 模式 时,返回的纯 HTML 里只有元标签,其余内容都被 JavaScript 对象包裹,这对搜索引擎抓取不友好,不利于 SEO 优化;而用 next export 命令能生成搜索引擎可读的纯 HTML。想咨询:有没有可行方案,在使用 SSR、SSG 或增量式 SSG 渲染模式的同时,让客户端/浏览器获取到纯 HTML 源码?

当前项目的 _app.tsx 代码

import React, { useEffect } from 'react';
import Router from 'next/router';
import { useStore } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';
import { ThemeProvider } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
import { CacheProvider } from '@emotion/react';
import { ApolloProvider } from '@apollo/client';
import NProgress from 'nprogress';
import 'nprogress/nprogress.css';
import initializeApollo from '@/graphql';
import { wrapper } from '../src/store';
import { setClientState } from '../src/store/redux-models/common/actions';
import theme from '../src/theme/theme';
import createEmotionCache from '../src/createEmotionCache';
import '../styles/globals.css';
import EnvValidation from '/src/utils/env-validator';

if (typeof window === 'undefined') {
  EnvValidation();
}

// Client-side cache, shared for the whole session of the user in the browser.
const clientSideEmotionCache = createEmotionCache();

if (typeof process.env.NEXT_PUBLIC_API_MOCKING !== 'undefined') {
  if (process.env.NEXT_PUBLIC_API_MOCKING === 'enabled') {
    // eslint-disable-next-line global-require
    require('@/mocks');
  }
}

function MyApp({
  Component,
  emotionCache = clientSideEmotionCache,
  pageProps,
}) {
  const store = useStore();

  useEffect(() => {
    store.dispatch(setClientState());
  }, []);

  Router.events.on('routeChangeStart', () => {
    NProgress.start();
  });

  Router.events.on('routeChangeComplete', () => {
    NProgress.done();
  });

  return (
    <PersistGate persistor={store.__persistor}>
      <CacheProvider value={emotionCache}>
        <ThemeProvider theme={theme}>
          {/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
          <CssBaseline />
          <ApolloProvider client={initializeApollo()}>
            <Component {...pageProps} />
          </ApolloProvider>
        </ThemeProvider>
      </CacheProvider>
    </PersistGate>
  );
}

export default wrapper.withRedux(MyApp);

解决方案

Next.js 的 SSR、SSG 本身就支持输出完整的纯 HTML,你遇到的问题是因为项目中的状态管理(Redux Persist)和数据获取(Apollo Client)没有正确适配服务端渲染逻辑,导致服务端仅渲染了元标签,核心内容被客户端渲染流程阻塞。以下是具体修复方案:

1. 确保页面数据在服务端预获取

不同渲染模式下,需要用对应的方法在服务端提前获取数据,让服务端能渲染出完整内容:

  • SSG(静态生成):在页面组件中使用 getStaticProps,构建时预获取数据并生成完整 HTML
  • SSR(服务端渲染):在页面组件中使用 getServerSideProps,每次请求时在服务端获取数据并渲染 HTML
  • 增量式 SSG:结合 getStaticProps 和 revalidate 参数,既预渲染页面,又能在指定时间后自动更新内容

例如,在页面中用 getServerSideProps 预获取 Apollo 数据:

import { initializeApollo } from '@/graphql';
import { gql } from '@apollo/client';

export async function getServerSideProps() {
  const apolloClient = initializeApollo();
  
  // 预获取页面所需数据
  await apolloClient.query({
    query: gql`
      query GetPageData {
        // 你的查询内容
      }
    `,
  });

  return {
    props: {
      // 将 Apollo 缓存状态传递给客户端,避免重复请求
      apolloState: apolloClient.cache.extract(),
    },
  };
}

2. 修复 Redux Persist 和 Apollo 的服务端渲染逻辑

  • Redux PersistGate:PersistGate 会等待 Redux 持久化状态加载完成后再渲染内容,但服务端没有本地存储,不需要等待持久化。可以在服务端渲染时跳过 PersistGate,直接渲染页面内容:
  • Apollo Client:需要将服务端预获取的缓存状态传递给客户端实例,确保服务端渲染的内容和客户端 hydrated 的内容一致

修改 MyApp 代码:

function MyApp({
  Component,
  emotionCache = clientSideEmotionCache,
  pageProps,
}) {
  const store = useStore();
  const isServer = typeof window === 'undefined';

  useEffect(() => {
    store.dispatch(setClientState());
  }, []);

  Router.events.on('routeChangeStart', () => {
    NProgress.start();
  });

  Router.events.on('routeChangeComplete', () => {
    NProgress.done();
  });

  // 服务端渲染时跳过 PersistGate,直接渲染内容
  const appContent = (
    <CacheProvider value={emotionCache}>
      <ThemeProvider theme={theme}>
        <CssBaseline />
        {/* 使用传递过来的 apolloState 初始化客户端,确保服务端数据同步 */}
        <ApolloProvider client={initializeApollo(pageProps.apolloState)}>
          <Component {...pageProps} />
        </ApolloProvider>
      </ThemeProvider>
    </CacheProvider>
  );

  return isServer ? appContent : (
    <PersistGate persistor={store.__persistor}>
      {appContent}
    </PersistGate>
  );
}

3. 排查客户端专属代码

确保页面组件的渲染逻辑不依赖 window、document 等客户端专属对象,服务端渲染时能正常执行。如果必须使用客户端 API,要放在 useEffect 或客户端专属的代码分支中。

总结

通过正确配置服务端数据预获取、调整状态管理组件的服务端渲染逻辑,就能在 SSR/SSG/增量式 SSG 模式下输出搜索引擎友好的纯 HTML,无需依赖 next export。

内容的提问来源于stack exchange,提问作者ledoc

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.23 07:36:32