You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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的最顶部添加以下代码:
    require('@babel/register')({
      presets: ['@babel/preset-env', '@babel/preset-react']
    });
    
    这段代码会给Node的require钩子增加转译能力,后续引入的JS、JSX文件都会先经过Babel转译再执行。

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.06 07:30:01