React SSR运行报ReferenceError: document is not defined错误如何解决?
错误原因
- 核心问题是Node.js服务端环境不存在浏览器专属的
document全局对象,代码里两处触发了该对象调用:- 路由层面:
App.js中直接全局使用了BrowserRouter,其底层依赖的createBrowserHistory方法会调用document相关API,服务端执行renderToString渲染App组件时直接触发报错 - 构建配置层面:
webpack.server.js中针对css使用了style-loader,该loader的逻辑是动态往document.head插入style标签,服务端执行时同样会找不到document报错
- 路由层面:
- 额外兼容问题:当前使用的是react-router-dom v6版本,现有App.js里的
Route写法不符合v6规范,component属性已废弃,需改为element属性传组件实例。
修复步骤
1. 路由逻辑拆分
修改App.js,移除内部的BrowserRouter包裹,调整Route写法
import React from 'react'; import { Route, Routes, Link} from "react-router-dom"; import Home from './Home'; import About from './About'; export default () => { return ( <> <nav> <Link to="/"> Home </Link> <Link to="/about"> About </Link> <Link to="/product"> Product </Link> </nav> <Routes> <Route exact path="/" element={<Home />} /> <Route path="/about" element={<About />} /> </Routes> </> ); };
修改客户端index.js,用BrowserRouter包裹App
import React from "react"; import ReactDOM from "react-dom"; import App from "./App"; import { BrowserRouter as Router } from "react-router-dom"; ReactDOM.hydrate( <React.StrictMode> <Router> <App /> </Router> </React.StrictMode>, document.getElementById("root") );
修改服务端渲染逻辑(server/index.js),用StaticRouter包裹App
服务端渲染时需要拿到当前请求的path传给StaticRouter的location属性,示例代码:
import { StaticRouter } from 'react-router-dom/server'; // 其他依赖引入逻辑省略 app.get('/*', (req, res) => { const appHtml = ReactDOMServer.renderToString( <StaticRouter location={req.path}> <App /> </StaticRouter> ) // 后续拼接完整HTML返回的逻辑省略 })
2. 修复服务端webpack配置
安装mini-css-extract-plugin,替换掉服务端css规则里的style-loader:
const path = require('path'); const nodeExternals = require('webpack-node-externals'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); module.exports = { entry: './server/index.js', target: 'node', externals: [nodeExternals()], output: { path: path.resolve('server-build'), filename: 'index.js' }, mode: 'development', module: { rules: [ { test: /\.js|jsx$/, use: 'babel-loader', exclude: /(node_modules|bower_components)/, }, { test: /\.css$/i, use: [MiniCssExtractPlugin.loader, "css-loader"], }, { test: /\.link\.css$/i, use: [ { loader: "style-loader", options: { injectType: "linkTag" } }, { loader: "file-loader" }, ], }, ] }, plugins: [new MiniCssExtractPlugin()] };
如果需要实现同构样式也可以选择isomorphic-style-loader,根据自身业务场景选择即可。
内容的提问来源于stack exchange,提问作者user3300505
相关产品推荐
相关产品推荐

