preactjs / preact-ssr-prepass

Drop-in replacement for react-ssr-prepass
49 stars 7 forks source link

preact-ssr-prepass

npm Coverage Status OpenCollective Backers OpenCollective Sponsors travis

Drop-in replacement for react-ssr-prepass.

Neither Preact nor React support Suspense on the server as of now. Heavily inspired by react-ssr-prepass, preact-ssr-prepass provides a two-pass approach with which Suspense can be used on the server. In the first pass, preact-ssr-prepass will create a VNode tree and await all suspensions, in the second pass preact-render-to-string can be used to render a vnode to a string.

Even if preact-ssr-prepass is designed to do as little as possible, it still adds a slight overhead since the VNode tree is created twice.

⚠️ Note that this is neither an official Preact nor React API and that the way Suspense is handled on the server might/will change in the future!

Usage / API

Awaiting suspensions

preact-ssr-prepass needs to be called just before rendering a vnode to a string. See the following example:

lazy.js:

export default function LazyLoaded() {
    return <div>I shall be loaded and rendered on the server</div>
}

index.js:

import { createElement as h } from 'preact';
import { Suspense, lazy } from 'preact/compat';
import renderToString from 'preact-render-to-string';
import prepass from 'preact-ssr-prepass';

const LazyComponent = lazy(() => import('./lazy'));

const vnode = (
    <Suspense fallback={<div>I shall not be rendered on the server</div>}>
        <LazyComponent />
    </Suspense>
);

prepass(vnode)
    .then(() => {
        // <div>I shall be loaded and rendered on the server</div>
        console.log(renderToString(vnode));
    });

Custom suspensions/data fetching using the visitor

preact-ssr-prepass accepts a second argument that allows you to suspend on arbitrary elements:

ssrPrepass(<App />, (element, instance) => {
  if (instance !== undefined && typeof instance.fetchData === 'function') {
    return instance.fetchData()
  }
});

API

/**
 * Visitor function to suspend on certain elements.
 * 
 * When this function returns a Promise it is awaited before the vnode will be rendered.
 */
type Visitor = (element: preact.VNode, instance: ?preact.Component) => ?Promise<any>;

/**
 * The default export of preact-ssr-prepass
 *
 * @param{vnode} preact.VNode The vnode to traverse
 * @param{visitor} ?Visitor A function that is called for each vnode and might return a Promise to suspend.
 * @param{context} ?Object Initial context to be used when traversing the vnode tree
 * @return Promise<any> Promise that will complete once the complete vnode tree is traversed. Note that even if
 *         a Suspension throws the returned promise will resolve.
 */
export default function prepass(vnode: preact.VNode, visitor?: Visitor, context:? Object): Promise<any>;

Replace react-ssr-prepass (e.g. next.js)

react-ssr-prepass is usually used on the server only and not bundled into your bundles but rather required through Node.js. To alias react-ssr-prepass to preact-ssr-prepass we recommend to use module-alias:

Create a file named alias.js:

const moduleAlias = require('module-alias')

module.exports = () => {
  moduleAlias.addAlias('react-ssr-prepass', 'preact-ssr-prepass')
}

Require and execute the exported function in your applications entrypoint (before require'ing react-ssr-prepass):

require('./alias')();

Differences to react-ssr-prepass

The visitor passed to preact-ssr-prepass gets a Preact element instead of a React one. When you use preact/compat's createElement it will make the element/vnode look as similar to a React element as possible.