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

React SSR应用路由组件正常渲染但页面跳转时URL不更新问题求助

问题根因

  • 全局使用MemoryHistory作为路由实例:该类型history仅将路由状态存储在内存中,不会和浏览器地址栏做任何交互,所以跳转时URL不会更新,仅适合非浏览器环境、测试场景使用。
  • 服务端渲染逻辑错误:你在服务启动时就一次性渲染好了App的内容,没有根据用户请求的路径动态渲染对应页面,且express仅处理了根路径的SSR请求,其他路径无法触发正确的服务端渲染。
  • 服务端和客户端复用了同一个history实例:服务端每次请求都需要独立的history实例,否则会出现不同用户请求互相干扰的问题。

修复方案

1. 修改App.js

移除内部硬编码的history实例,改为从props接收,补全缺失的导入,修正组件名大小写问题:

import React, { Component } from 'react';
import { Router, Switch, Route, Redirect} from 'react-router-dom';
import { Home } from './components/Home';
import { About } from './components/about';
import { ErrorFindingPage } from './components/ErrorFindingPage';
import NavMenu from './components/NavMenu';
import './custom.css';

export default class App extends Component {
    static displayName = App.name;

    render() {
        const { history } = this.props;
        return (
            <Router history={history}>
                <NavMenu />
                    <Switch>
                        <Route exact path='/' component={Home} />
                        <Route path='/about' component={About} />
                        <Route path='/ErrorFindingPage' component={ErrorFindingPage} />
                        <Redirect to="/ErrorFindingPage" />
                    </Switch>
            </Router>
        );
    }
}

2. 补充/修改客户端入口文件(一般为src/index.js或client.js)

客户端使用BrowserHistory实现地址栏同步,使用hydrate复用服务端渲染的DOM节点:

import React from 'react';
import ReactDOM from 'react-dom';
import { createBrowserHistory } from 'history';
import App from './App';

const history = createBrowserHistory();
ReactDOM.hydrate(
  <App history={history} />,
  document.getElementById('root')
);

3. 修改server.js

每次请求动态创建独立的history实例,匹配所有路径做SSR处理:

import express from "express";
import fs from "fs";
import path from "path";
import React from "react";
import ReactDOMServer from "react-dom/server";
import {Helmet} from "react-helmet";
import { createMemoryHistory } from 'history';

import App from "../src/App";

const PORT = process.env.port || 8080;
const app = express();

// 优先托管静态资源,设置index:false避免直接返回未处理的index.html
app.use(express.static(path.resolve(__dirname, '../build'), { index: false }))

// 匹配所有请求路径,动态渲染对应页面
app.get("*", (req, res, next) => {
  // 每次请求创建独立的memoryHistory,初始路径设为当前请求路径
  const history = createMemoryHistory({
    initialEntries: [req.path]
  });

  const htmlcontents = ReactDOMServer.renderToString(<App history={history} />);
  const helmet = Helmet.renderStatic();

  fs.readFile(path.resolve("./build/index.html"), "utf-8", (err, data) => {
    if (err) {
      console.log(err);
      return res.status(500).send("Some error happened");
    }
    return res.send(
      data.replace(
        '<div id="root"></div>',
        `<div id="root">${htmlcontents}</div>`
      )
      .replace("</head>", `${helmet.meta.toString()}${helmet.title.toString()}</head>`)
    );
  });
});

app.listen(PORT, () => {
  console.log(`App launched on ${PORT}`);
});

其他注意事项

  • React组件名必须首字母大写,你之前定义的about组件改为About,否则会被识别为原生HTML标签导致渲染异常。
  • 跳转路由时要使用react-router-dom提供的Link组件、useHistory/useNavigate等API,不要直接修改window.location,否则会触发页面刷新,破坏SPA体验。

内容的提问来源于stack exchange,提问作者NineO

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.30 19:57:03