Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
599 views
in Technique[技术] by (71.8m points)

react-router 4 - Browser history needs a DOM

I am trying server side rendering using react-router 4. I am following the example provided here https://reacttraining.com/react-router/web/guides/server-rendering/putting-it-all-together

As per the example on server we should use StaticRouter. When I import as per the example I am seeing StaticRouter as undefined

import {StaticRouter} from 'react-router';

After doing some research online I found I could use react-router-dom. Now my import statement looks like this.

import {StaticRouter} from 'react-router-dom';

However when I run the code I am getting Invariant Violation: Browser history needs a DOM in the browser.

my server.js file code

....
app.get( '*', ( req, res ) => {
  const html = fs.readFileSync(path.resolve(__dirname, '../index.html')).toString();
  const context = {};
  const markup = ReactDOMServer.renderToString(
    <StaticRouter location={req.url} context={context} >
      <App/>
    </StaticRouter>
  );

  if (context.url) {
    res.writeHead(302, {
      Location: context.url
    })
    res.end();
  } else {
      res.send(html.replace('$react', markup));
  }
} );
....

And my client/index.js code

....
ReactDOM.render((
  <BrowserRouter>
    <App />
  </BrowserRouter>
), root);
....

Update v1 Reduced my example to a bear minimum and still getting the same error.

clientIndex.js

import ReactDOM from 'react-dom'
import { BrowserRouter } from 'react-router-dom'
import App from '../App'

ReactDOM.render((
  <BrowserRouter>
    <App/>
  </BrowserRouter>
), document.getElementById('app'))

serverIndex.js

import { createServer } from 'http'
import React from 'react'
import ReactDOMServer from 'react-dom/server'
import { StaticRouter } from 'react-router'
import App from '../App'

createServer((req, res) => {
  const context = {}

  const html = ReactDOMServer.renderToString(
    <StaticRouter
      location={req.url}
      context={context}
    >
      <App/>
    </StaticRouter>
  )

res.write(`
  <!doctype html>
  <div id="app">${html}</div>
`)
res.end()
}).listen(3000);

App.js

import React from 'react';
import { BrowserRouter as Router, Route } from 'react-router-dom';
import routes from "./client/routes";
const App = ( ) => (
  <Router>
    <Route path="/" exact render={( props ) => ( <div>Helloworld</div> )} />
  </Router>
)

export default App;
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You need to use different history provider for server side rendering because you don't have a real DOM (and browser's history) on server. So replacing BrowserRouter with Router and an alternate history provider in your app.js can resolve the issue. Also you don't have to use two wrappers. You are using BrowserRouter twice, in app.js as well as clientIndex.js which is unnecessary.

import { Route, Router } from 'react-router-dom';
import { createMemoryHistory } from 'history';

const history = createMemoryHistory();

  <Router history={history}>
   <Route path="/" exact render={( props ) => ( <div>Helloworld</div> )} />
  </Router>

You can now replace StaticRouter with ConnectedRouter which can be used both in client and server. I use the following code to choose between history and export it to be used in ConnectedRouter's history.

export default (url = '/') => {
// Create a history depending on the environment
  const history = isServer
    ? createMemoryHistory({
        initialEntries: [url]
     })
   : createBrowserHistory();
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...