react-snap is a free, open source blogging & personal sites project written in JavaScript and released under MIT. It has 5,117 GitHub stars, 396 forks and 192 open issues, and was last pushed 8 months ago. On this registry it ranks #11 of 25 tracked projects in Blogging & Personal Sites, with 5 head-to-head comparisons available. It gained 1 stars over the last 3 tracked days.

What is react-snap?

What it is

react-snap is a JavaScript tool that pre-renders a single-page application into static HTML. It operates in the npm and JavaScript ecosystem as a framework-agnostic prerendering utility, and it gives SPAs generated markup that search engines and social crawlers can read from static HTML.

The concrete problem it solves is that many SPAs serve a minimal HTML shell, which can make indexing and social media optimization less reliable. react-snap addresses this by launching Headless Chrome, visiting the application, and saving the resulting HTML. The README also describes load performance optimization as part of the prerendering workflow.

Key capabilities

  • react-snap pre-renders a web application into static HTML by using Headless Chrome as the rendering environment.
  • It crawls available links starting from the root, so multiple routes can be captured as static pages.
  • It enables SEO for Google and DuckDuckGo, and SMO for Twitter and Facebook, by producing readable HTML.
  • It can be added to create-react-app through a postbuild script, with hydration logic used to attach the existing app to prerendered markup.
  • It uses a real browser, which the README says avoids problems with unsupported HTML5 features such as WebGL or Blobs, and it does not depend on React.

Who uses it and how

  • React developers using create-react-app install react-snap, configure a postbuild step, and adjust the entry file so the browser can hydrate the generated HTML.
  • Vue.js developers use react-snap to prerender a built dist folder, often setting source and minifyHtml options.
  • Preact developers use the tool with a hydration pattern that reuses the first child of the root element when prerendered markup is present.
  • Applications using Redux or asynchronous components can use window.snapSaveState to capture state at the end of rendering.
  • Teams comparing prerendering approaches may use the README example showing a switch from prerender-spa-plugin to react-snap.

Getting started

The README shows installation with yarn add --dev react-snap, followed by adding react-snap as a postbuild script in package.json. For create-react-app React 16+ usage, the entry file checks whether the root element already has child nodes and then calls hydrate or render.

When to use it โ€” and when not to

Use react-snap when a JavaScript SPA needs static HTML for SEO or SMO and uses HTML5 history routing, because the tool does not support hash or hash-bang URLs. Avoid it when hash-based routing is required, when a project cannot tolerate the Vue rehydration caveat, or when the 192 open issues and missing contributor data suggest a maintenance risk.

project readme (upstream, from github) โ€” read inline

Stand With Ukraine

react-snap Build Status npm npm Twitter Follow

Pre-renders a web app into static HTML. Uses Headless Chrome to crawl all available links starting from the root. Heavily inspired by prep and react-snapshot, but written from scratch. Uses best practices to get the best loading performance.

๐Ÿ˜ Features

  • Enables SEO (Google, DuckDuckGo...) and SMO (Twitter, Facebook...) for SPAs.
  • Works out-of-the-box with create-react-app - no code-changes required.
  • Uses a real browser behind the scenes, so there are no issues with unsupported HTML5 features, like WebGL or Blobs.
  • Does a lot of load performance optimization. Here are details, if you are curious.
  • Does not depend on React. The name is inspired by react-snapshot but works with any technology (e.g., Vue).
  • npm package does not have a compilation step, so you can fork it, change what you need, and install it with a GitHub URL.

Zero configuration is the main feature. You do not need to worry about how it works or how to configure it. But if you are curious, here are details.

Basic usage with create-react-app

Install:

yarn add --dev react-snap

Change package.json:

"scripts": {
  "postbuild": "react-snap"
}

Change src/index.js (for React 16+):

import { hydrate, render } from "react-dom";

const rootElement = document.getElementById("root");
if (rootElement.hasChildNodes()) {
  hydrate(<App />, rootElement);
} else {
  render(<App />, rootElement);
}

That's it!

Basic usage with Preact

To do hydration in Preact you need to use this trick:

const rootElement = document.getElementById("root");
if (rootElement.hasChildNodes()) {
  preact.render(<App />, rootElement, rootElement.firstElementChild);
} else {
  preact.render(<App />, rootElement);
}

Basic usage with Vue.js

Install:

yarn add --dev react-snap

Change package.json:

"scripts": {
  "postbuild": "react-snap"
},
"reactSnap": {
  "source": "dist",
  "minifyHtml": {
    "collapseWhitespace": false,
    "removeComments": false
  }
}

Or use preserveWhitespace: false in vue-loader.

source - output folder of webpack or any other bundler of your choice

Read more about minifyHtml caveats in #142.

Example: Switch from prerender-spa-plugin to react-snap

Caveats

Only works with routing strategies using the HTML5 history API. No hash(bang) URLs.

Vue uses the data-server-rendered attribute on the root element to mark SSR generated markup. When this attribute is present, the VDOM rehydrates instead of rendering everything from scratch, which can result in a flash.

This is a small hack to fix rehydration problem:

window.snapSaveState = () => {
  document.querySelector("#app").setAttribute("data-server-rendered", "true");
};

window.snapSaveState is a callback to save the state of the application at the end of rendering. It can be used for Redux or async components. In this example, it is repurposed to alter the DOM, this is why I call it a "hack." Maybe in future versions of react-snap, I will come up with better abstractions or automate this process.

Vue 1.x

Make sure to use replace: false for root components

โœจ Examples

โš™๏ธ Customization

If you need to pass some options for react-snap, you can do this in your package.json like this:

"reactSnap": {
  "inlineCss": true
}

Not all options are documented yet, but you can check defaultOptions in index.js.

inlineCss

Experimental feature - requires improvements.

react-snap can inline critical CSS with the help of minimalcss and full CSS will be loaded in a non-blocking manner with the help of loadCss.

Use inlineCss: true to enable this feature.

TODO: as soon as this feature is stable, it should be enabled by default.

โš ๏ธ Caveats

Async components

Also known as code splitting, dynamic import (TC39 proposal), "chunks" (which are loaded on demand), "layers", "rollups", or "fragments". See: Guide To JavaScript Async Components

An async component (in React) is a technique (typically implemented as a higher-order component) for loading components on demand with the dynamic import operator. There are a lot of solutions in this field. Here are some examples:

It is not a problem to render async components with react-snap, the tricky part happens when a prerendered React application boots and async components are not loaded yet, so React draws the "loading" state of a component, and later when the component is loaded, React draws the actual component. As a result, the user sees a flash:

100%                    /----|    |----
                       /     |    |
                      /      |    |
                     /       |    |
                    /        |____|
  visual progress  /
                  /
0%  -------------/

Usually a code splitting library provides an API to handle it during SSR, but as long as "real" SSR is not used in react-snap - the issue surfaces, and there is no simple way to fix it.

  1. Use react-prerendered-component. This library holds onto the prerendered HTML until the dynamically imported code is ready.
import loadable from "@loadable/component";
import { PrerenderedComponent } from "react-prerendered-component";

const prerenderedLoadable = dynamicImport => {
  const LoadableComponent = loadable(dynamicImport);
  return React.memo(props => (
    // you can use the `.preload()` method from react-loadable or react-imported-component`
    <PrerenderedComponent live={LoadableComponent.load()}>
      <LoadableComponent {...props} />
    </PrerenderedComponent>
  ));
};

const MyComponent = prerenderedLoadable(() => import("./MyComponent"));

MyComponent will use prerendered HTML to prevent the page content from flashing (it will find the required piece of HTML using an id attribute generated by PrerenderedComponent and inject it using dangerouslySetInnerHTML).

  1. The same approach will work with React.lazy, but React.lazy doesn't provide a prefetch method (load or preload), so you need to implement it yourself (this can be a fragile solution).
const prefetchMap = new WeakMap();
const prefetchLazy = LazyComponent => {
  if (!prefetchMap.has(LazyComponent)) {
    prefetchMap.set(LazyComponent, LazyComponent._ctor());
  }
  return prefetchMap.get(LazyComponent);
};

const prerenderedLazy = dynamicImport => {
  const LazyComponent = React.lazy(dynamicImport);
  return React.memo(props => (
    <PrerenderedComponent live={prefetchLazy(LazyComponent)}>
      <LazyComponent {...props} />
    </PrerenderedComponent>
  ));
};

const MyComponent = prerenderedLazy(() => import("./MyComponent"));
  1. use loadable-components 2.2.3 (current is >5). The old version of loadable-components can solve this issue for a "snapshot" setup:
import { loadComponents, getState } from "loadable-components";
window.snapSaveState = () => getState();

loadComponents()
  .then(() => hydrate(App

readme truncated โ€” read the full docs on github

Frequently asked questions

Is react-snap free to use?

react-snap is open source under the MIT licence. There is no licence fee and no seat count โ€” you can self-host it or, where the project offers one, pay a vendor for a managed version instead.

What does react-snap do?

๐Ÿ‘ป Zero-configuration framework-agnostic static prerendering for SPAs

What is react-snap written in?

react-snap is primarily written in JavaScript. Its source is publicly available at https://github.com/stereobooster/react-snap, and it has 5,117 GitHub stars.