Package Exports
- react-ssr
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (react-ssr) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
react-ssr
Overview
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.
Installation
$ npm install react-ssr --saveWe 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"
]
}Getting started
Hopefully you can get a simple page server-rendering in minutes. Efficiently. Here's everything you need to know.
Setting up the server
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)Setting up the routes
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'))Fetching data
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 |
Example repos
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
No babel plugin?
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 MyPageAnd your done.
Options
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 } |
Notes
As data fetching occurs before rendering begins, you should consider:
- You can't access
thisinside 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
fetchDatawill not be server-side rendered. So, if you're programatically doing something like the below, it will render withthis.props.loadingas true on the client, then fetch the data and rerender:
const DynamicComponent = components['MyComponent']
return <DynamicComponent />Contributing
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. 👌