Nocturnally Psychologizing Millipede

    @rest-hooks/ssr
    TypeScript icon, indicating that this package has built-in type declarations

    0.7.8 • Public • Published

    Rest Hooks Server Side Rendering helpers

    CircleCI Coverage Status npm downloads bundle size npm version PRs Welcome Chat

    Hydrate/dehydration utilities for Rest Hooks

    Server side

    import express from 'express';
    import { renderToPipeableStream } from 'react-dom/server';
    import {
      createPersistedStore,
      createServerDataComponent,
    } from '@rest-hooks/ssr';
    
    const rootId = 'react-root';
    
    const app = express();
    app.get('/*', (req: any, res: any) => {
      const [ServerCacheProvider, useReadyCacheState, controller] =
        createPersistedStore();
      const ServerDataComponent = createServerDataComponent(useReadyCacheState);
    
      controller.fetch(NeededForPage, { id: 5 });
    
      const { pipe, abort } = renderToPipeableStream(
        <Document
          assets={assets}
          scripts={[<ServerDataComponent key="server-data" />]}
          rootId={rootId}
        >
          <ServerCacheProvider>{children}</ServerCacheProvider>
        </Document>,
    
        {
          onCompleteShell() {
            // If something errored before we started streaming, we set the error code appropriately.
            res.statusCode = didError ? 500 : 200;
            res.setHeader('Content-type', 'text/html');
            pipe(res);
          },
          onError(x: any) {
            didError = true;
            console.error(x);
            res.statusCode = 500;
            pipe(res);
          },
        },
      );
      // Abandon and switch to client rendering if enough time passes.
      // Try lowering this to see the client recover.
      setTimeout(abort, 1000);
    });
    
    app.listen(3000, () => {
      console.log(`Listening at ${PORT}...`);
    });

    Client

    import { hydrateRoot } from 'react-dom';
    import { awaitInitialData } from '@rest-hooks/ssr';
    
    const rootId = 'react-root';
    
    awaitInitialData().then(initialState => {
      hydrateRoot(
        document.getElementById(rootId),
        <CacheProvider initialState={initialState}>{children}</CacheProvider>,
      );
    });

    NextJS

    We've optimized integration into NextJS with a custom Document and NextJS specific wrapper for App

    pages/_document.tsx
    import { RestHooksDocument } from '@rest-hooks/ssr/nextjs';
    
    export default RestHooksDocument;
    pages/_app.tsx
    import { AppCacheProvider } from '@rest-hooks/ssr/nextjs';
    
    export default function App({ Component, pageProps }: AppProps) {
      return (
        <AppCacheProvider>
          <Component {...pageProps} />
        </AppCacheProvider>
      );
    }

    Further customizing Document

    To further customize Document, simply extend from the provided document.

    Make sure you use super.getInitialProps() instead of Document.getInitialProps() or the Rest Hooks code won't run!

    pages/_document.tsx
    import { Html, Head, Main, NextScript } from 'next/document'
    import { RestHooksDocument } from '@rest-hooks/ssr/nextjs';
    
    export default class MyDocument extends RestHooksDocument {
      static async getInitialProps(ctx) {
        const originalRenderPage = ctx.renderPage
    
        // Run the React rendering logic synchronously
        ctx.renderPage = () =>
          originalRenderPage({
            // Useful for wrapping the whole react tree
            enhanceApp: (App) => App,
            // Useful for wrapping in a per-page basis
            enhanceComponent: (Component) => Component,
          })
    
        // Run the parent `getInitialProps`, it now includes the custom `renderPage`
        const initialProps = await super.getInitialProps(ctx)
    
        return initialProps
      }
    
      render() {
        return (
          <Html>
            <Head />
            <body>
              <Main />
              <NextScript />
            </body>
          </Html>
        )
      }
    }

    CSP Nonce

    Rest Hooks Document serializes the store state in a script tag. In case you have Content Security Policy restrictions that require use of a nonce, you can override RestHooksDocument.getNonce.

    Since there is no standard way of handling nonce in NextJS, this allows you to retrieve any nonce you created in the DocumentContext to use with Rest Hooks.

    pages/_document.tsx
    import { RestHooksDocument } from '@rest-hooks/ssr/nextjs';
    
    export default class MyDocument extends RestHooksDocument {
      static getNonce(ctx: DocumentContext) {
        // this assumes nonce has been added here - customize as you need
        return ctx.res.nonce;
      }
    }

    API

    createPersistedStore(managers) => [ServerCacheProvider, useReadyCacheState, controller, store]

    Used to server side render cache. Renders <ServerDataComponent/> inside to serialize cache so client can hydrate.

    createServerDataComponent(useReadyCacheState, id = 'rest-hooks-data')

    Contents are a script with JSON encoding of cache state sent from server. Be sure to place outside hydration element so React will not need to hydrate it.

    getInitialData(id = 'rest-hooks-data') => Promise(State)

    Resolves promise with serialized initialState to pass to <CacheProvider />

    Install

    npm i @rest-hooks/ssr

    DownloadsWeekly Downloads

    420

    Version

    0.7.8

    License

    Apache-2.0

    Unpacked Size

    100 kB

    Total Files

    55

    Last publish

    Collaborators

    • ntucker
    • ljharb