React 18搭配Next.js12.3.1:Suspense、createRoot相关问题排查
核心说明
Next.js 12.2+ 已经内置适配 React 18,不需要手动修改 ReactDOM.render 为 createRoot——Next.js 会在内部处理入口渲染逻辑,所以你找不到传统 React 项目里的 index.tsx 入口代码是正常的。
解决 Suspense Hydration 错误
你遇到的错误是因为 Suspense 边界在服务端渲染内容完成客户端 hydration 前,就收到了状态更新,导致切换到纯客户端渲染。可以按以下步骤修复:
1. 用 startTransition 包裹页面渲染逻辑
在 _app.tsx 中利用 React 18 的 useTransition Hook,把页面组件的渲染包裹在 startTransition 中,延迟非紧急更新,避免打断 hydration:
import { useTransition } from 'react'; import { ChakraProvider } from '@chakra-ui/react'; import { ApolloProvider } from '@apollo/client'; import theme from '../theme'; import { apolloClient } from '../apollo/client'; function MyApp({ Component, pageProps, getLayout }) { const [isPending, startTransition] = useTransition(); return ( <ChakraProvider theme={theme}> <ApolloProvider client={apolloClient}> {getLayout( <React.Suspense fallback={<div>Loading...</div>}> {isPending ? ( <div>Loading...</div> ) : ( <>{startTransition(() => <Component {...pageProps} />)}</> )} </React.Suspense> )} </ApolloProvider> </ChakraProvider> ); } export default MyApp;
2. 排查页面组件的同步更新逻辑
检查你的页面组件(Component 对应的页面文件),是否存在在组件挂载前(比如直接在组件函数体中)同步触发状态更新的代码,这类操作会在 hydration 过程中触发 Suspense 边界更新。
如果有这类逻辑,要么把它移到 useEffect 中(确保在 hydration 完成后执行),要么用 startTransition 包裹状态更新的代码。
3. 确保 Apollo Client 数据预取正确
如果使用 Apollo Client,要保证页面的 getStaticProps/getServerSideProps 已经预取好所需数据,避免客户端 hydration 时重新发起查询导致状态突变。比如在页面中通过 getServerSideProps 预取 Apollo 数据,确保服务端渲染的内容和客户端 hydration 的内容一致。
4. 验证依赖版本兼容性
确认你的 react、react-dom 版本都是 18.x,同时 @apollo/client、@chakra-ui/react 等依赖也已更新到支持 React 18 的版本,版本不兼容也可能导致 hydration 异常。
内容的提问来源于stack exchange,提问作者Mel

