如何使用Node.js + React.js实现服务端渲染(SSR)?
React 服务端渲染启动报错排查
问题背景
使用React.js创建前端项目,后端采用Node.js开发,已经通过Webpack完成前端项目的构建。在给React应用接入服务端渲染时启动报错,无法正常运行。
相关代码
server.js
const express = require('express'); const path = require('path'); const logger = require('morgan'); const bodyParser = require('body-parser'); const cors = require('cors'); const { renderToString } = require("react-dom/server"); const { StaticRouter } = require("react-router-dom") const React = require("react"); const App = require('./src/App') const session = require('express-session'); const routes = require('./server/index'); const port = process.env.PORT || 3000; const app = express(); app.use(express.static(path.join(__dirname, 'dist'))); app.use(cors()); app.use(logger('dev')); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: false })); //app.use(cookieParser()); app.use(function (req, res, next) { // Website you wish to allow to connect res.setHeader('Access-Control-Allow-Origin', '*'); // Request methods you wish to allow res.setHeader('Access-Control-Allow-Methods', 'GET,POST'); // Request headers you wish to allow res.setHeader( 'Access-Control-Allow-Headers', 'X-Requested-With,content-type', ); // Set to true if you need the website to include cookies in the requests sent // to the API (e.g. in case you use sessions) res.setHeader('Access-Control-Allow-Credentials', true); // Pass to next layer of middleware next(); }); // end app.use('/', routes); app.use((req, res, next) => { var err = new Error('Not Found'); err.status = 404; next(err); }); if (app.get('env') === 'development') { app.use((err, req, res, next) => { res.status(err.status || 500); res.json({ message: err.message, error: err, }); }); } app.use((err, req, res, next) => { res.status(err.status || 500); res.json({ message: err.message, error: {}, }); }); router.get("*", (req, res) => { // Render the component to a string. const html = ReactDOMServer.renderToString( <StaticRouter location={req.url} context={context}> <App /> </StaticRouter> ); const finalDocument =`<html> <head> <title>Your App Title</title> </head> <body> <div id="root">${html}</div> </body> </html> `; res.send(finalDocument); }); app.listen(port, () => { console.log('Api Server running on port' + port); }); module.exports = app;
package.json
{ "name": "ssrnodereact", "version": "0.1.0", "private": true, "dependencies": { "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.11.2", "@testing-library/jest-dom": "^5.14.1", "@testing-library/react": "^11.2.7", "@testing-library/user-event": "^12.8.3", "body-parser": "^1.19.0", "cors": "^2.8.5", "cryptr": "^6.0.2", "dotenv": "^10.0.0", "express": "^4.17.1", "express-session": "^1.17.2", "formik": "^2.2.9", "helmet": "^4.6.0", "morgan": "^1.10.0", "nodemailer": "^6.6.3", "path": "^0.12.7", "pg": "^8.7.1", "prettier": "^2.3.2", "react": "^17.0.2", "react-dom": "^17.0.2", "react-ga": "^3.3.0", "react-router-dom": "^5.2.0", "react-scripts": "4.0.3", "typescript": "^4.3.5", "web-vitals": "^1.1.2", "yup": "^0.32.9" }, "scripts": { "build": "webpack --mode production", "start": "npm run build && nodemon server.js", "test": "react-scripts test", "eject": "react-scripts eject", "format": "prettier --write ./**/*.{js,jsx,ts,tsx,css,md,json,html} --config ./.prettierrc" }, "eslintConfig": { "extends": [ "react-app", "react-app/jest" ] }, "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] }, "devDependencies": { "@babel/core": "^7.15.0", "@babel/preset-env": "^7.15.0", "@babel/preset-react": "^7.14.5", "babel-loader": "^8.2.2", "file-loader": "^6.2.0", "html-webpack-plugin": "^3.2.0", "react-hot-loader": "^4.13.0", "ts-loader": "^8.3.0", "webpack": "^4.44.0", "webpack-cli": "^4.7.2", "webpack-dev-server": "^3.11.2" } }
报错信息

解决方案
1. 修复JSX语法无法解析的问题
Node.js原生不支持解析JSX语法,直接运行server.js时,代码内的<StaticRouter>、<App />等JSX语法会触发意外token报错,需要给Node端增加Babel转译能力:
- 安装依赖:
npm install @babel/register --save-dev - 在server.js的最顶部添加以下代码:
这段代码会给Node的require钩子增加转译能力,后续引入的JS、JSX文件都会先经过Babel转译再执行。require('@babel/register')({ presets: ['@babel/preset-env', '@babel/preset-react'] });
2. 修复代码中未定义的变量
- 已经从
react-dom/server中解构导入了renderToString方法,直接调用即可,不需要使用ReactDOMServer.renderToString - 渲染前需要初始化
context变量,在renderToString调用前添加const context = {}; - 没有定义
router变量,把router.get("*")改为app.get("*")
3. 修复路由匹配顺序
当前把SSR的通配符路由写在了404中间件之后,请求会优先匹配到404逻辑,永远不会触发SSR渲染。把app.get("*")的代码块移动到app.use('/', routes);之后、404中间件之前即可。
内容的提问来源于stack exchange,提问作者sudesh sharma
相关产品推荐
相关产品推荐

