Next.js SSR/SSG模式下如何输出纯HTML以优化SEO?
问题描述
我正在使用 ^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

