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.
npm install react-router-server --save
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.
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/>;
})
}
/>
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() {
...
}
}
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')
]
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'));
});
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(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.
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)
The Match component is the same as the react-router Match component but can be used for async render methods.
preloadModules(modules)
modules: array of modules passed by the server side to the client side for preloading.
Async version of ReactDOM.renderToString.
renderToString(element, context)
element: The element to render
context: The server context.
The ServerStateProvider component is used for providing the server state
to the client side. Provided by the state
prop.
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.
Everyone is welcome to contribute and add more components/documentation whilst following the contributing guidelines.
React Router Server is licensed under The MIT License (MIT).