Creates a static HTML snapshot for your create-react-app
app, to improve initial page load times. Makes for blazing fast load times, and improves SEO.
Work in progress: This module is under development. Many features that you'd need for production use aren't implemented yet. However, you are encouraged to give this module a try to get a sense of the OMG speed. Feedback and contributions welcome!
Inspired by react-snapshot
and react-snap
. This module obsesses over the performance of your built app.
Install using npm:
npm install snapshotify
Modify the scripts
in your package.json to add a "postbuild"
key:
{
...
"scripts": {
...
"postbuild": "snapshotify"
...
}
}
Then, modify your index.js
to wait before hydration:
import React from 'react';
import { hydrate, render } from 'react-dom';
import loadScripts from 'snapshotify';
import App from './App';
const rootElement = document.getElementById('root');
if (rootElement.hasChildNodes()) {
loadScripts().then(() => hydrate(<App />, rootElement));
} else {
render(<App />, rootElement);
}
Build your app as usual:
npm run build
express
to serve your built create-react-app
app.puppeteer
to launch a headless browser to render your app.postcss
to parse the CSS into an AST.
@font-face
.::before
or :hover
for nodes that are on page.CSSO
. Embeds this into the markup of your HTML directly as an inline style tag.<link rel='preload' as='font' type='font/woff2' href=''>
.main.hash.js
), and includes them instead as <link rel='preload' as='script' href='main.hash.js' />
.<link rel='preload'>
instead. After page load, a CSS loader injected in the page adds the external CSS files. A fallback is also added, for browsers that don't support JS, so that styles don't appear broken for non-JS browsers.<meta http-equiv='Content-Security-Policy' content='...'>
, hashes are added to your script-src
and style-src
directives, to allow the added inline scripts and styles.html-minifier
to minify the whole resulting HTML, making several micro-optimisations.react-router
), and optimises those pages too.As a result of the optimisations above, the page load time is drastically improved. Only the markup, fonts (if any), and images (if any) are actually needed for page load. External script files (typically your main.js
) and external CSS files (index.css
) are not injected into the page until after page load, so that they do not contribute to the page load time. Even so, the script files, all of the needed code-split chunks, external CSS files and fonts are donwloaded as soon as possible, and all in parallel, so that your page's startup time is as quick as possible, and your JS starts up faster than usual.
When invoking snapshotify
, you can pass an additional parameter to specify your config file:
snapshotify --config snapshot.json
If you don't specify a --config
, it defaults to snapshot.json
. If the config file can't be found, defaults as specified below are used.
snapshot.json
The config file can have any of the following properties:
inlineCSS
: (boolean, default true
) Extracts the minimal CSS required for the initial render of the page, and inlines it as a style tag. You may want to disable this if the minimal CSS is already in the DOM as a style tag, which might be the case if you're using a CSS-in-JS lib that uses style tags.preloadScripts
: (boolean, default true
) Preloads your script file(s) using <link rel='preload' as='script' href='...'>
, and injects an inline script loader to execute your code only after script preloading is complete. If your initial render depends on one or more dynamically import
ed components, all the code-split chunks as preloaded as well. The combination of the preloading and the script loader ensures that you get to the window-onload event as soon as possible.preloadFonts
: (boolean, default true
) Identifies the fonts needed for the initial render of the page, and preloads them using a <link rel='preload' as='font' type='font/woff2' href=''>
. Only preloads a woff2
font, since all browsers that support preloading also support (and prefer) a woff2
font.addCSPHashes
: (boolean, default true
) If your page has a CSP <meta>
tag, setting this flag adds style-src
and script-src
hashes to the CSP policy, to allow the inline style and script tags added during the snapshot process. If the CSP meta tag isn't present, this rule has no effect. If any of the directives has 'unsafe-inline'
already present, hashes aren't added.cspAlgo
: (string, default sha256
) The algorithm to be used for calculating the CSP hash. Possible values are sha256
, sha384
or sha512
. This property is only used if addCSPHashes
is true
.The following properties are mostly only useful for debugging:
dryRun
: (boolean, default false
) Does a dry run. Doesn't write any files to the build
directory. Just generates a report after processing your app.printConsoleLogs
: (boolean, default false
) Prints console.log
lines from your app, as reported by puppeteer
, to stdout.Sometimes, it may be necessary to detect if you are running in the snapshot mode, so that you can serve up alternative content. To enable this, a global window.SNAPSHOT
variable is set to true
when taking the snapshot.
glamor
.import(...)
(say through react-loadable
) are handled automatically. These modules are added to the page as <link rel='preload', as='script' href='n.chunk.hash.js />
, so that they are loaded alongside your main.js
in parallel. (Normally chunks aren't requested until after main.js
has been downloaded and evaluated.)In my setup, I view the resulting snapshot app using nginx. The following is my nginx config.
server {
listen 80;
server_name domain.test;
# Strip .html and trailing slash from the URL
rewrite ^(/.*)\.html(\?.*)?$ $1$2 permanent;
rewrite ^/(.*)/$ /$1 permanent;
root /path/to/app/build;
index index.html;
try_files $uri/index.html $uri.html $uri/ $uri =404;
gzip on;
gzip_vary on;
gzip_comp_level 9;
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/javascript application/xml text/html;
error_page 404 /404.html;
error_page 500 502 503 504 /500.html;
}
License: MIT