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
相关产品推荐
相关产品推荐

