使用 React Router 进行服务器端渲染“浏览器历史记录需要一个 DOM”

Posted

技术标签:

【中文标题】使用 React Router 进行服务器端渲染“浏览器历史记录需要一个 DOM”【英文标题】:Server-side rendering with React Router "Browser history needs a DOM" 【发布时间】:2021-01-06 19:48:09 【问题描述】:

我已经通过互联网扫描了两天,但我无法理解使用 react-routes 实现 s-s-r 的要点。我花了 5 天的时间才弄明白这一切没有路线。但现在我无法进一步理解使用 react-routes。这是必要的细节-

server/server.js

import express from 'express';
import fs from 'fs';
import path from 'path';

import React from 'react';
import ReactDOMServer from 'react-dom/server';
import StaticRouter from 'react-router-dom';

import App from '../src/App';

const PORT = process.env.PORT || 8080;

const app = express();

app.use(express.static(path.resolve('./build')))

app.get('*', (req, res) => 

    fs.readFile(path.resolve('./build/index.html'), 'utf-8', (err, data) => 

    if (err) 
      console.log(err);
      return res.status(500).send('Some error happened');
        

        const context = ;
        const app = ReactDOMServer.renderToString(
            <StaticRouter location=req.url context=context>
                <App />
            </StaticRouter>
        )
        
    return res.send(
      data.replace(
        '<div id="root"></div>',
        `<div id="root">$app</div>`
      )
    );
    );
    
);

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

服务器/index.js

require('ignore-styles')

require('@babel/register')(
    ignore: [/(node_module)/],
    presets: ['@babel/preset-env', '@babel/preset-react']
)

require('./server')

src/App.js

import React from "react";
import BrowserRouter, Switch, Route, Link from 'react-router-dom';
import "./App.css";

function App() 

  return (
    <BrowserRouter>
      <Switch>
        <Route path='/' exact component=() => <Link to='/about'>This is Home. Go to About.</Link> />
        <Route path='/about' component=() => <Link to='/'>This is About. Go to Home.</Link> />
      </Switch>
    </BrowserRouter>
  );


export default App;

src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';

ReactDOM.hydrate(<App />, document.getElementById('root'));

serviceWorker.unregister();

【问题讨论】:

【参考方案1】:

从应用程序中删除BrowserRouter 并将其放入您的索引文件中。

index.js

ReactDOM.hydrate(
       <BrowserRouter>
          <App />
       </BrowserRouter>, 
   document.getElementById('root')
);

App.js

import React from "react";
import  Switch, Route, Link from 'react-router-dom';
import "./App.css";

function App() 

  return (
      <Switch>
        <Route path='/' exact component=() => <Link to='/about'>This is Home. Go to About.</Link> />
        <Route path='/about' component=() => <Link to='/'>This is About. Go to Home.</Link> />
      </Switch>
  );

因为&lt;App /&gt; 包含BrowserRouter 并且您正在服务器中使用它。 BrowserRouter 无法在服务器中处理,因为它需要 DOM。

另外,从react-router 而非react-router-dom 导入您的StaticRouter

您可以查看文档here

import  StaticRouter  from 'react-router';

【讨论】:

有什么区别?它仍然会通过水合物。 hydrate 函数未在服务器中运行。它在客户端(浏览器)中运行。 无论如何他在他的 server.js 文件中使用 react router dom,我认为这是问题所在。 哦!我错过了,这也是问题之一。 基本上,StaticRouter 在服务器上工作,BrowserRouter 在客户端工作。而已。由于 BrowserRouter 隐式地从客户端的window.location 获取位置,我们需要显式地将位置传递给 StaticRouter。在那之后,一切都一样了。

以上是关于使用 React Router 进行服务器端渲染“浏览器历史记录需要一个 DOM”的主要内容,如果未能解决你的问题,请参考以下文章

如何在逻辑上结合 react-router 和 redux 进行客户端和服务器端渲染

React Router v5 伴随着代码拆分和使用服务器端渲染的数据预取

使用 react-router v4 和 express.js 进行服务器渲染

使用 redux 和 react 进行异步服务器端渲染

尽管所有反应路由器版本冲突,如何在反应中进行服务器端渲染?

服务器端 react-router 不会渲染我的路由