JSPM

  • Created
  • Published
  • Downloads 3081
  • Score
    100M100P100Q108818F
  • License MIT

A simplified solution to server side rendering React applications.

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

build status npm version license dependency status Coverage via Codecov JavaScript Style Guide

Overview

react-ssr is a minimalistic solution to achieve server-side rendering with a few lines of code and a simple ruleset. The simple ruleset is outlined with performance in mind, and must be followed to server side render React apps effectively. 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 --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"
  ]
}

Adding to your server

Firstly, you'll need to use the module on your Node server and have some static routes of your app setup. The below example uses express:

  • Your Node JS express server server.js
import express from 'express'
import ssr from 'react-ssr'
import routes from './routes'

const app = express()
const renderer = ssr({ routes })

app.get('*', renderer) // send all routes to ssr
  • Static routes of your React app routes.js
import HomePage from './HomePage'
import NotFoundPage from './NotFoundPage'

const routes = [
  {
    path: '/',
    exact: true,
    component: HomePage
  },
  {
    path: '/about',
    redirectTo: '/'
  },
  {
    path: '**',
    component: NotFoundPage
  }
]

export default routes

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 to use a special method for this. It's a static method that sits in your React component called fetchData. react-ssr will execute this before it begins rendering your app on the server and inject the result of it into the components props.

Here's an example:

const pageContent = () => new Promise((resolve, reject) => {
  fetch('/api')
    .then(res => res.json())
    .then(resolve)
    .catch(reject)
})

class Navigation extends React.Component {
  static fetchData ({req, match}) {
    // params is an object of params from the matched React route
    return {
      content: pageContent() // becomes available as this.props.content
    }
  }

  render () {
    console.log(this.props.content)
    return <span />
  }

🏆 You should now have server-side rendering setup. Unless you chose not to install the babel plugin. Keep reading if you haven't used the babel plugin.

Without babel plugin

Raise an issue if you'd like an alternative to the babel plugin. Anyway, without it, here's what 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/lib/fetchData'

class MyComponent extends React.Component {
  static fetchData () {}
}

export default ssrFetchData(MyComponent)
  • Your route/page/top-level components should have a waitsFor static array containing components required for fetchData calls, e.g:
class MyPage extends React.Component {
  render () {
    return (
      <Example />
    )
  }
}

MyPage._ssrWaitsFor = [
  Example,
  SomeOtherChildWithStaticFetchData
]

export default MyPage

And your done.

Notes

There's a few things to consider here. Since data fetching occurs before rendering begins, you'll have these points to deal with:

  • You can't access this inside your static fetchData. Chain API calls together in parent components if they are dependent.
  • You must use static routes. Dynamic routing (react router v4) takes place as your app is rendering, but this has huge performance implications for server side rendering. So, we must have a static set of routes that we can match against before rendering begins.
  • Components that are dynamically rendered with static fetchData will not be server-side rendered. So, if you're programatically doing something like this, it won't server-side render, but instead show a loading spinner and client-side render:
const DynamicComponent = components['MyComponent']
return <DynamicComponent />

Also, there's a couple of caveats for now. We're working on them:

  • Only tested with React Router v4 static routing for now. v3 or less support will likely be added soon. Ultimately we recommend you upgrade
  • Untested on anything but flat routes currently (about to move onto nested)
  • Your React components must must be an export default, higher order components should be wrapped with decorators, rather than inline around the class name:
@myDecorator
class MyComponentName extends Component {}

export default MyComponentName

There is a way to avoid the above if you absolutely must. There are rare cases where you can't use the decorator, or you might just not be in a position to use them for some reason. Import the HOC manually and define it like below, ensuring you use a unique variable name for the wrapped instance (even if you use the babel plugin, it will see this and skip it):

import fetchData from 'react-ssr/lib/fetchData'
import { observer } from 'mobx-react'

class MyComponent extends Component {}

const variableWithUniqueName = observer(MyComponent)
export default fetchData(variableWithUniqueName)

Options

Option Description Required Default
routes static routes array of your react app yes []
disable disables server-side rendering no false
Html override core html document template no see src/components/DefaultTemplate in repo

License

MIT