react-router-server codeclimate editorconfig eslint travis-ci npm

Server Side Rendering library for React Router v4.

React Router Server

Build Status Code Climate Dependency Status peerDependency Status devDependency Status npm downloads npm version Gitter

Server Side Rendering library for React Router v4. Allows to do code-splitting using webpack and to fetch data. Renders the page on the server side and resolves all issues with mounting on the client side.

Table Of Content

  1. Installation
  2. Example
  3. Usage
  4. API
  5. Webpack Bundle Support
  6. Contributing
  7. License

Installation

npm install react-router-server –save

Example

A working example is provided in the example directory of this project. To try for yourself, you can clone this project and run npm start. This will provide a server accessible at http://localhost:3000.

Usage

Loading code splitted module

To load a module splitted by webpack use the importModule method and the Match component provided by this library.

import { Match, importModule } from 'react-router-server';

<Match
  exactly
  pattern="/test"
  render={matchProps => importModule('moduleName', './module', () => System.import("./module"))
    .then(module => {
      const Component = module.default;
      return <Component/>;
    })
  }
/>

Loading props on the server side

To load props for your components to render on the server side, use the fetchState decorator.

@fetchState(
  state => ({
    isLoaded: state.user ? true : false,
    user: state.user
  }),
  actions => ({
    done: actions.done
  })
)
class MyComponent extends Component {
  componentWillMount() {
    if (!this.props.isLoaded) {
      loadAsyncUser()
        .then(user => this.props.done({ user }));
    }
  }
  
  render() {
    ...
  }
}

Server Side

You need to use the renderToString provided by this library:

import renderToString from 'react-router-server';
import { ServerRouter, createServerRenderContext } from 'react-router'

const context = createServerRenderContext();

renderToString(
    <ServerRouter
      location={'/current/path/' /* provide the request url */}
      context={context}
    >
      <App/>
    </ServerRouter>
).then(html => console.log(html)); // send html

An initial state and modules to preload will be passed through the context. You will need to pass these to your HTML template to preload the modules and pass the initialState to the client side.

import stats from './stats.json';

const initialState = context.getInitialState();
const modules = context.getModules(stats);

You will need to get the webpack stats to extract the modules from webpack. To do this, you can use the stats-webpack-plugin and add this line to your webpack config plugins.

  plugins: [
    new StatsPlugin('stats.json')
  ]

Client Side

Preload the modules in your HTML file if you are using code-splitting. Pass the initial state and modules to your app.

<link rel="preload" href="/path/to/module" as="script">

<script>
  window.__INITIAL_STATE__ = ...;
  window.__INITIAL_MODULES__ = ...;
</script>

Preload the modules in your JS before rendering the app.

import React from 'react';
import { BrowserRouter } from 'react-router';
import { render } from 'react-dom';
import { preloadModules, ServerStateProvider } from 'react-router-server';

preloadModules(__INITIAL_MODULES__).then(() => {
  render((
    <ServerStateProvider state={__INITIAL_STATE__}>
      <BrowserRouter>
        <App/>
      </BrowserRouter>
    </ServerStateProvider>
  ), document.getElementById('main'));
});

API

fetchState

fetchState(mapStateToProps, mapActionsToProps)

mapStateToProps(state): function to map the state provided by the done action to props in your component;

mapActionsToProps(actions): function to map the actions to props in your component; Currently, only the done action exists and is used when you are finished fetching props.

importModule

importModule(name, path, systemImport)

name: Unique name of your module.

path: Path to your module relative to the current file. Same as the path in the systemImport param.

systemImport: A function returning a promise with your System.import("./path/to/your/module") call.

importWebpackBundle

importModule(appSystemImport, moduleSystemImport)

systemImport: A function returning a promise with your System.import("./path/to/your/app.bundle") call.

moduleSystemImport: A function returning a promise with your System.import("./path/to/your/0.module.bundle") call. E.G. (path) => System.import('./' + path)

Match

The Match component is the same as the react-router Match component but can be used for async render methods.

preloadModules

preloadModules(modules)

modules: array of modules passed by the server side to the client side for preloading.

renderToString

Async version of ReactDOM.renderToString.

renderToString(element, context)

element: The element to render

context: The server context.

ServerStateProvider

The ServerStateProvider component is used for providing the server state to the client side. Provided by the state prop.

Webpack Bundle Support

Server Side Rendering (SSR) is nice, but your app is probably complicated and uses Webpack loaders to load CSS or other types of files. Traditionally, this has always been complicated for SSR because those Webpack loaders are not supported by NodeJS.

To work around this limitation, you can import your Webpack bundle directly into your React Router Server application and do SSR on the bundle instead of importing the unbundled files.

To do this, you will need to create a bundle for the server app and a bundle for the client app, you will need the Webpack stats on both of them to cross reference the modules, as they won’t be the same for the server and the client.

React Router Server provides a importWebpackBundle method to import the bundle in your server.

Here’s a simple example of how this works:

import { ServerRouter, createServerRenderContext } from 'react-router';
import { renderToString, importWebpackBundle } from 'react-router-server';
import serverStats from './stats/server.json';
import clientStats from './stats/client.json';

importWebpackBundle(
  () => System.import('./app'), // path to your your server bundle
  (path) => System.import(`./${path}`) // callback for module imports inside your app
)
  then(({ default: App }) => {
    const context = createServerRenderContext();
    renderToString(
        <ServerRouter
          location={'/current/path/' /* provide the request url */}
          context={context}
        >
          <App/>
        </ServerRouter>
    )
      .then(html => {
        const result = context.getResult();
        const initialState = context.getInitialState();
        const modules = context.getModules(serverStats, clientStats); // you need to provide stats for the server bundle and the client bundle
        
        // send data to client, with the initial state and preloading the modules
      })
  });

To use System.import in your server script, take a look at the babel-plugin-system-import-transformer Babel plugin.

Contributing

Everyone is welcome to contribute and add more components/documentation whilst following the contributing guidelines.

License

React Router Server is licensed under The MIT License (MIT).


Top Contributors

gabrielbull cmmartin yormi

Releases

-   1.1.1 zip tar
-   1.1.0 zip tar
-   1.0.5 zip tar
-   1.0.4 zip tar
-   1.0.3 zip tar
-   1.0.2 zip tar
-   1.0.1 zip tar
-   1.0.0 zip tar
-   0.5.0 zip tar
-   0.4.0 zip tar
-   0.3.0 zip tar
-   0.2.2 zip tar
-   0.2.1 zip tar
-   0.2.0 zip tar
-   0.1.4 zip tar
-   0.1.3 zip tar
-   0.1.2 zip tar
-   0.1.1 zip tar
-   0.1.0 zip tar