33

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;
Saf
  • 331
  • 1
  • 3
  • 4
  • 3
    I managed to solve it. I don't need to wrap my inside in App.js. Because I am was already wrapping it in clientIndex.js – Saf Mar 28 '17 at 04:49
  • I'm dealing with routing for the first time now... I think you might be able to install the [history](https://www.npmjs.com/package/history) package and use `createMemoryHistory()` and pass that to the 'history' property (i.e. `` – Jason Goemaat Mar 29 '17 at 04:10
  • @Saf thanks a lot.. your solution worked like a charm and i was looking for and idol setup of isomorphic structure but didn't found anything working.. your code rocked..!! – Ritesh Apr 06 '17 at 07:38

3 Answers3

25

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();
}
10

In clientIndex.js

Rather than BrowserRouter use StaticRouter.

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

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

Community
  • 1
  • 1
Kooldandy
  • 457
  • 4
  • 12
  • 1
    changing it to StaticRouter resulted in a crash of node and my webpack watch...I don't think this is a good solution. – Kaboukie Feb 20 '20 at 22:26
5

As is essentially noted in the comments, one may hit this error (as I have) by accidentally wrapping your App component in a <BrowserRouter>, when instead it is your client app that should be wrapped.

App.js

import React from 'react'

const App = () => <h1>Hello, World.</h1>

export default App

ClientApp.js

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

const render = Component => {
  ReactDOM.render(
    <BrowserRouter>
      <Component />
    </BrowserRouter>,
    document.getElementById('app')
  )
}

render(App)

See also the React Router docs.

zgreen
  • 2,043
  • 17
  • 17
  • 1
    that doesn't answer the question and it's fairly vague, what's Component ? – Paul Brunache Sep 06 '17 at 02:50
  • `Component` is the React component taken by the `render` function. – zgreen Sep 06 '17 at 19:04
  • 1
    The question notes an error `Invariant Violation: Browser history needs a DOM`, that can occur from wrapping the `` around the incorrect component. [This is also noted here](https://stackoverflow.com/questions/43058684/react-router-4-browser-history-needs-a-dom/45043410?noredirect=1#comment73205862_43058684). – zgreen Sep 06 '17 at 19:10