Browser history needs a DOM when server side rendering

1.3k Views Asked by At

I am trying to implement server-side-rendering in my react app (using the create-react-app environment). I am trying to follow the example shown here, which still maintaining my app's structure. Not much different, so I figured it wouldn't be an issue to follow along within my own app. https://medium.com/@benlu/ssr-with-create-react-app-v2-1b8b520681d9

When I attempt to build the app, I just the following: Invariant Violation: Browser history needs a DOM

I've looked everywhere I can and can't seem to find what I am doing wrong.

client side index.js:

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

import { Provider } from 'react-redux'
import Store from './store'

import App from './App';
import registerServiceWorker from './registerServiceWorker';

ReactDOM.render(
 <Provider store={Store}>
  <BrowserRouter>
   <App />
  </BrowserRouter>
 </Provider>,
 document.getElementById('root')
);
registerServiceWorker();

server side app.js

require('ignore-styles')
const bodyParser = require('body-parser')
const compression = require('compression')
const express = require('express')
const morgan = require('morgan')
const path = require('path')
const fs = require('fs')

require('babel-register')({
 ignore: /\/(build|node_modules)\//,
 presets: ['env', 'react-app']
})

// routes
const index = require('./routes/index')
// const api = require('./routes/api')
const universalLoader = require('./universal')

const app = express()

// Support Gzip
app.use(compression())

// Support post requests with body data (doesn't support multipart, use multer)
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: false }))

// Setup logger
app.use(morgan('combined'))

app.use('/', index)

// Serve static assets
app.use(express.static(path.resolve(__dirname, '..', 'build')))

// app.use('/api', api)

// Always return the main index.html, so react-router render the route in the client
app.use('/', universalLoader)

module.exports = app

server side universal.js

const path = require('path')
const fs = require('fs')

const React = require('react')
const {Provider} = require('react-redux')
const {renderToString} = require('react-dom/server')
const {StaticRouter} = require('react-router-dom')

const {default: configureStore} = require('../src/store')
const {default: App} = require('../src/App')

module.exports = function universalLoader(req, res) {
 const filePath = path.resolve(__dirname, '..', 'build', 'index.html')

 fs.readFile(filePath, 'utf8', (err, htmlData)=>{
  if (err) {
   console.error('read err', err)
   return res.status(404).end()
  }
  const context = {}
  const store = configureStore()
  const markup = renderToString(
   <Provider store={store}>
    <StaticRouter
     location={req.url}
     context={context}
    >
     <App/>
    </StaticRouter>
   </Provider>
  )

  if (context.url) {
   // Somewhere a `<Redirect>` was rendered
   redirect(301, context.url)
  } else {
   // we're good, send the response
   const RenderedApp = htmlData.replace('{{SSR}}', markup)
   res.send(RenderedApp)
  }
 })
}

0

There are 0 best solutions below