NextJS结合React Context使用时页面源码无预渲染HTML的问题排查
Next.js 预渲染失效问题:Context 与 Redux-Persist 导致无预渲染 HTML
问题场景
在 Next.js 中使用 React Context(InternetConnectionStatusProvider)和 Redux-Persist 时,页面源码中看不到完整的预渲染 HTML,特定标签后无预渲染数据。当前 _app.js 的组件嵌套结构如下:
<Provider store={store}> <PersistGate persistor={persistor}> <InternetConnectionStatusProvider> <Component {...pageProps} /> {/* </InternetConnectionStatusProvider> */} </PersistGate> </Provider>
InternetConnectionStatusContext 代码:
const { createContext, useState, useEffect, useMemo } = require('react') export const InternetConnectionStatusContext = createContext({ isOnline: true, }) export const InternetConnectionStatusProvider = ({ children }) => { const [isOnline, setOnline] = useState(true) // if (typeof navigator === 'undefined') { // return null // } useEffect(() => { setOnline(navigator.onLine) window.addEventListener('online', () => { setOnline(true) }) window.addEventListener('offline', () => { setOnline(false) }) return () => { window.removeEventListener('online', () => { setOnline(true) }) window.removeEventListener('offline', () => { setOnline(false) }) } }, [navigator.onLine]) const value = useMemo(() => ({ isOnline })) return ( <InternetConnectionStatusContext.Provider value={value}> {children} </InternetConnectionStatusContext.Provider> ) }
问题根源
- Context 端问题:Next.js 预渲染(SSG/SSR)在服务端执行,服务端不存在
navigator、window等浏览器专属 API。你的 Provider 中直接在useEffect依赖里使用navigator.onLine,服务端渲染时会触发状态异常;如果启用注释掉的判断,会直接返回null,完全阻断子组件预渲染。 - Redux-Persist 端问题:
PersistGate默认会等待 localStorage 加载完成后才渲染子组件,但服务端没有 localStorage,会一直处于加载等待状态,导致服务端无法生成子组件的预渲染 HTML。
解决方案
一、修复 InternetConnectionStatusProvider
修改 Provider,仅在客户端执行浏览器相关逻辑,服务端直接用初始状态渲染子组件:
const { createContext, useState, useEffect, useMemo } = require('react') export const InternetConnectionStatusContext = createContext({ isOnline: true, }) export const InternetConnectionStatusProvider = ({ children }) => { const [isOnline, setOnline] = useState(true) useEffect(() => { // 仅在客户端挂载时执行 const handleOnline = () => setOnline(true) const handleOffline = () => setOnline(false) // 初始化网络状态 setOnline(navigator.onLine) // 添加监听事件 window.addEventListener('online', handleOnline) window.addEventListener('offline', handleOffline) // 组件卸载时清理监听 return () => { window.removeEventListener('online', handleOnline) window.removeEventListener('offline', handleOffline) } }, []) // 空依赖确保仅客户端挂载执行一次 const value = useMemo(() => ({ isOnline }), [isOnline]) // 依赖isOnline避免无效更新 return ( <InternetConnectionStatusContext.Provider value={value}> {children} </InternetConnectionStatusContext.Provider> ) }
二、修复 Redux-Persist 预渲染问题
修改 _app.js,区分服务端和客户端渲染逻辑,服务端直接渲染子组件,客户端再加载持久化数据:
import { useEffect, useState } from 'react' import { Provider } from 'react-redux' import { PersistGate } from 'redux-persist/integration/react' import { store, persistor } from '../store' import { InternetConnectionStatusProvider } from '../context/InternetConnectionStatus' function MyApp({ Component, pageProps }) { const [isClient, setIsClient] = useState(false) useEffect(() => { setIsClient(true) }, []) // 服务端渲染:直接渲染,不等待持久化加载 if (!isClient) { return ( <Provider store={store}> <InternetConnectionStatusProvider> <Component {...pageProps} /> </InternetConnectionStatusProvider> </Provider> ) } // 客户端渲染:加载持久化数据后渲染 return ( <Provider store={store}> <PersistGate loading={null} persistor={persistor}> <InternetConnectionStatusProvider> <Component {...pageProps} /> </InternetConnectionStatusProvider> </PersistGate> </Provider> ) } export default MyApp
三、验证效果
运行 next build && next export(SSG)或启动服务访问 SSR 页面,查看页面源码,确认子组件的 HTML 已正常预渲染。
内容的提问来源于stack exchange,提问作者Mic Scofield
相关产品推荐
相关产品推荐

