Server Side Rendering
Install
npm install @loadable/server && npm install --save-dev @loadable/babel-plugin @loadable/webpack-plugin# or using yarnyarn add @loadable/server && yarn add --dev @loadable/babel-plugin @loadable/webpack-plugin
Guide
@loadable/babel-plugin
1. Install .babelrc
{"plugins": ["@loadable/babel-plugin"]}
@loadable/webpack-plugin
2. Install webpack.config.js
const LoadablePlugin = require('@loadable/webpack-plugin')module.exports = {// ...plugins: [new LoadablePlugin()],}
ChunkExtractor
server-side
3. Setup import { ChunkExtractor } from '@loadable/server'// This is the stats file generated by webpack loadable pluginconst statsFile = path.resolve('../dist/loadable-stats.json')// We create an extractor from the statsFileconst extractor = new ChunkExtractor({ statsFile })// Wrap your application using "collectChunks"const jsx = extractor.collectChunks(<YourApp />)// Render your applicationconst html = renderToString(jsx)// You can now collect your script tagsconst scriptTags = extractor.getScriptTags() // or extractor.getScriptElements();// You can also collect your "preload/prefetch" linksconst linkTags = extractor.getLinkTags() // or extractor.getLinkElements();// And you can even collect your style tags (if you use "mini-css-extract-plugin")const styleTags = extractor.getStyleTags() // or extractor.getStyleElements();
loadableReady
client-side
4. Add Loadable components loads all your scripts asynchronously to ensure optimal performances. All scripts are loaded in parallel, so you have to wait for them to be ready using loadableReady
.
import { loadableReady } from '@loadable/component'loadableReady(() => {const root = document.getElementById('main')hydrate(<App />, root)})
🚀 Checkout the complete example in this repository
Collecting chunks
The basic API goes as follows:
import { renderToString } from 'react-dom/server'import { ChunkExtractor } from '@loadable/server'const statsFile = path.resolve('../dist/loadable-stats.json')const extractor = new ChunkExtractor({ statsFile })const html = renderToString(extractor.collectChunks(<YourApp />))const scriptTags = extractor.getScriptTags() // or extractor.getScriptElements();
The collectChunks
method wraps your element in a provider. Optionally you can use the ChunkExtractorManager
provider directly, instead of this method. Just make sure not to use it on the client-side.
import { renderToString } from 'react-dom/server'import { ChunkExtractor, ChunkExtractorManager } from '@loadable/server'const statsFile = path.resolve('../dist/loadable-stats.json')const extractor = new ChunkExtractor({ statsFile })const html = renderToString(<ChunkExtractorManager extractor={extractor}><YourApp /></ChunkExtractorManager>,)const scriptTags = extractor.getScriptTags() // or extractor.getScriptElements();
The extractor.getScriptTags()
returns a string of multiple <script>
tags marked as "async". You have to wait for them to be ready using loadableReady
.
Alternatively the ChunkExtractor
also has a getScriptElements()
method that returns an array of React elements.
Streaming rendering
Loadable is compatible with streaming rendering, if you use it you have to include script when the stream is complete.
import { renderToNodeStream } from 'react-dom/server'import { ChunkExtractor } from '@loadable/server'// if you're using express.js, you'd have access to the response object "res"// typically you'd want to write some preliminary HTML, since React doesn't handle thisres.write('<html><head><title>Test</title></head><body>')const statsFile = path.resolve('../dist/loadable-stats.json')const chunkExtractor = new ChunkExtractor({ statsFile })const jsx = chunkExtractor.collectChunks(<YourApp />)const stream = renderToNodeStream(jsx)// you'd then pipe the stream into the response object until it's donestream.pipe(res, { end: false })// and finalize the response with closing HTMLstream.on('end', () =>res.end(`${chunkExtractor.getScriptTags()}</body></html>`),)
Streaming rendering is not compatible with prefetch
<link>
tags.
Prefetching
Webpack prefetching is supported out of the box by Loadable. <link rel="preload">
and <link rel="prefetch">
can be added directly server-side to improve performances.
import path from 'path'import { ChunkExtractor, ChunkExtractorManager } from '@loadable/server'const statsFile = path.resolve('../dist/loadable-stats.json')const extractor = new ChunkExtractor({ statsFile })const jsx = extractor.collectChunks(<YourApp />)const html = renderToString(jsx)const linkTags = extractor.getLinkTags() // or chunkExtractor.getLinkElements();const html = `<html><head>${linkTags}</head><body><div id="root">${html}</div></body></html>`
It only works with
renderToString
API. Since<link>
must be added in the<head>
, you can't do it usingrenderToNodeStream
.
CSS
Extracted CSS using plugins like "mini-css-extract-plugin" are automatically collected, you can get them using getStyleTags
or getStyleElements
.
import { renderToString } from 'react-dom/server'import { ChunkExtractor } from '@loadable/server'const statsFile = path.resolve('../dist/loadable-stats.json')const extractor = new ChunkExtractor({ statsFile })const html = renderToString(extractor.collectChunks(<YourApp />))const styleTags = extractor.getStyleTags() // or extractor.getStyleElements();
Disable SSR on a specific loadable
Disable SSR on a specific loadable component with ssr: false
:
import loadable from '@loadable/component'// This dynamic import will not be processed server-sideconst Other = loadable(() => import('./Other'), { ssr: false })
stats.publicPath
at runtime
Override To override stats.publicPath
at runtime, pass in a custom publicPath
to the ChunkExtractor
constructor:
import { ChunkExtractor } from '@loadable/server'const statsFile = path.resolve('../dist/loadable-stats.json')const extractor = new ChunkExtractor({statsFile,publicPath: 'https://cdn.example.org/v1.1.0/',})
ChunkExtractor
entrypoints
When running your build, notice @loadable/webpack-plugin
generates a file called loadable-stats.json
, which contains information
about all your entries and chuncks from webpack.
Once that's in place, ChunkExtractor
will be responsible of finding your entries into this file.
The default behaviour of webpack, is to create an asset called main.js
if no named entry is specified, like so.
webpack.config.js
module.exports = {entry: './src/index.js',// ...}
Checkout webpack's entry naming configuration.
ChunkExtractor
will try to find yourmain.js
, and will look intoloadable-stats.json
to confirm it's there.
If for instance, your wish is to get a different named entry, you will need to pass an entrypoints
option.
const extractor = new ChunkExtractor({statsFile,entrypoints: ['client'], // array of webpack entries (default: ['main'])})
Using your own stats file
By default, the webpack plugin adds an asset to the webpack build called loadable-stats.json
. This contains the result of running webpack's stats.toJson()
with the following options:
{hash: true,publicPath: true,assets: true,chunks: false,modules: false,source: false,errorDetails: false,timings: false,}
stats.toJson()
is an expensive operation, and it can significantly slow down webpack watching recompiles.
If you already have a webpack stats file in your build that includes the necessary options, you may choose to use your existing stats object instead of creating a new one. You can do this as follows:
- pass your existing stats object into
ChunkExtractor
via thestats
option - disable both the
outputAsset
andwriteToDisk
options in the webpack plugin to prevent it from callingstats.toJson()