react-ssr
achieves React server-side rendering with a few lines of code and one simple rule. The rule is outlined with performance in mind, and must be followed to server side render React apps efficiently. It supports React Router 4, which introduced challenges to server-side rendering by making you have to declare data calls at a route level. react-ssr
allows you to make those calls at a component level.
$ npm install react-ssr --save
We recommend you use the babel plugin too. Add the babel plugin to your .babelrc
.
$ npm install babel-plugin-react-ssr --save-dev
{
"plugins": [
"react-ssr"
]
}
Hopefully you can get a simple page server-rendering in minutes. Efficiently. Here's everything you need to know.
Assuming you have a simple express server setup, you'll just need to hand off your routes to react-ssr. Bear in mind you can also pass a custom template that will be responsible for the 'HTML document' that wraps your React app, too. Copy the example from src/components/DefaultTemplate as a starting point.
import express from 'express'
import ssr from 'react-ssr'
import routes from './routes'
const app = express()
const renderer = ssr({
routes: routes
})
app.get('/*', renderer)
app.listen(8000)
You will need an array of static routes, which means each route will be an object (as per React Router v4's docs) and not a <Route />
. This is because a <Route />
can only be read once rendering begins. A static route can be matched against before rendering begins.
import HomePage from './pages/HomePage'
import NotFoundPage from './pages/NotFoundPage'
const routes = [
{
path: '/',
exact: true,
component: HomePage
},
{
path: '**',
component: NotFoundPage
}
]
Check out data loading with server side rendering in React Router v4 to see other comments or examples.
Additionally, your React app entry point will need to hydrate those routes out, for example: -
import React from 'react'
import ReactDOM from 'react-dom'
import BrowserRouter from 'react-router-dom/BrowserRouter'
import { renderRoutes } from 'react-router-config'
import routes from './routes'
const App = () => (
<BrowserRouter>
{renderRoutes(routes)}
</BrowserRouter>
)
ReactDOM.hydrate(<App />, document.getElementById('root'))
There's one important rule: If you want to make a data call, and you'd like it to be server side rendered correctly, you'll need a static fetchData
method. react-ssr
will execute this before it begins rendering your app on the server and inject the result of it into the components props.
Heads up! We're using the static keyword below. You'll need to add the transform class properties babel plugin or another alternative to use this at the time of writing.
Here's an example:
const getNavItems = () => {
return new Promise((resolve, reject) => {
fetch('/api/navigation')
.then(res => res.json())
.then(resolve)
.catch(reject)
})
})
class Navigation extends React.Component {
static fetchData ({req, res, match}) {
if (req && req.thing) {
res.redirect() // you can redirect the request
}
return {
content: getNavItems() // becomes available as this.props.content
}
}
render () {
console.log(this.props.content)
return <span />
}
}
// alternative syntax...
Navigation.fetchData = ({req, res, match}) => {
return {
content: getNavItems() // becomes available as this.props.content
}
}
🏆 You should now have server-side rendering setup. Keep reading if you haven't used the babel plugin.
Note that as per the above examples, fetchData
has an object parameter with some values in:
static fetchData ({match, req}) {}
Value | Description |
---|---|
match | React route that was matched, contains params |
req | Node JS request object, server side only |
res | Node JS response object, server side only |
Check out the example playground repository. It includes a basic Webpack setup with recommended babel plugins. More examples to follow, please raise an issue if you'd like something more urgently.
See https://github.com/oayres/react-ssr-examples
Raise an issue if you'd like an alternative to the babel plugin. Without it, here's the two things you'll need to do:
- Any component with a static fetchData must be wrapped at the bottom with our higher order component:
import ssrFetchData from 'react-ssr/fetchData'
class MyComponent extends React.Component {
static fetchData () {}
}
export default ssrFetchData(MyComponent)
- Your route/page/top-level components should have an ssrWaitsFor static array containing components required for fetchData calls, e.g:
import Example from './Example'
import OtherChildWithStaticFetchData from './OtherChildWithStaticFetchData'
class MyPage extends React.Component {
render () {
return (
<Example />
)
}
}
MyPage.ssrWaitsFor = [
Example,
OtherChildWithStaticFetchData
]
export default MyPage
And your done.
What you can pass to react-ssr
when you call it on the server. Example:
import ssr from 'react-ssr'
const renderer = ssr({
routes: [],
disable: false,
debug: false,
ignore: [
'/example/route' // sends route without ssr if matched
],
cache: { // currently experimental - only accepts redis as a store
mode: 'full|none', // full means entire page is cached
duration: 1800, // cache duration in seconds, will rerender and set it again after this time for a given route
redisClient: null // optional redisClient - ioredis or node_redis - to use redis as store
}
})
Option | Description | Required | Default |
---|---|---|---|
routes | static routes array of your react app | yes | [] |
disable | disables server-side rendering | no | false |
debug | adds more verbose logging to requests | no | false |
Html | override core html document template | no | see src/components/DefaultTemplate in repo |
Providers | wraps your routes, useful for context providers, etc | no | |
cache | allows caching of components or pages | no | { mode: 'none', duration: 1800 } |
As data fetching occurs before rendering begins, you should consider:
- You can't access
this
inside your staticfetchData
.- If you have some API call that needs data from another call, chain them together one after the other using a Promise or async await.
- Components that are dynamically rendered with static
fetchData
will not be server-side rendered. So, if you're programatically doing something like the below, it will render withthis.props.loading
as true on the client, then fetch the data and rerender:
const DynamicComponent = components['MyComponent']
return <DynamicComponent />
This package is still early doors. Please do get involved, feel free to critique it, offer solutions that can change its approach slightly, or request examples on how you want to use it. Spotted a bug, need something adding? Raise an issue. Pull requests welcome. 👌