Socket
Book a DemoInstallSign in
Socket

@data-client/ssr

Package Overview
Dependencies
Maintainers
0
Versions
28
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@data-client/ssr

Server Side Rendering helpers for Data Client

0.12.15
latest
Source
npmnpm
Version published
Maintainers
0
Created
Source

Reactive Data Client

CircleCI Coverage Status npm downloads npm version PRs Welcome Chat

Hydrate/dehydration utilities for Server Side Rendering with the Reactive Data Client

Usage

Integration with

  • NextJS
  • Anansi
    npx @anansi/cli hatch my-project
    
  • ExpressJS

For more details, see the Server Side Rendering docs page.

NextJS

App Router

Place DataProvider in your root layout

app/layout.tsx
import { DataProvider } from '@data-client/ssr/nextjs';
import { AsyncBoundary } from '@data-client/react';

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <DataProvider>
          <header>Title</header>
          <AsyncBoundary>{children}</AsyncBoundary>
          <footer></footer>
        </DataProvider>
      </body>
    </html>
  );
}

Any endpoints can be used in server components by simply awaiting them

export default async function StaticPage() {
  const todos = await TodoResource.getList();
  return <TodoList todos={todos} />;
}

Or used in client components - meaning their state can be mutated client-side with useSuspense()

'use client';
import { useSuspense } from '@data-client/react';

export default function InteractivePage() {
  const todos = useSuspense(TodoResource.getList);
  return <TodoList todos={todos} />;
}

Pages Router

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

pages/_document.tsx
import { DataClientDocument } from '@data-client/ssr/nextjs';

export default DataClientDocument;
pages/_app.tsx
import { AppDataProvider } from '@data-client/ssr/nextjs';

export default function App({ Component, pageProps }: AppProps) {
  return (
    <AppDataProvider>
      <Component {...pageProps} />
    </AppDataProvider>
  );
}

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 Data Client code won't run!

pages/_document.tsx
import { Html, Head, Main, NextScript } from 'next/document'
import { DataClientDocument } from '@data-client/ssr/nextjs';

export default class MyDocument extends DataClientDocument {
  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

Data Client 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 DataClientDocument.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 Data Client.

pages/_document.tsx
import { DataClientDocument } from '@data-client/ssr/nextjs';

export default class MyDocument extends DataClientDocument {
  static getNonce(ctx: DocumentContext) {
    // this assumes nonce has been added here - customize as you need
    return ctx.res.nonce;
  }
}

Express JS

Server side

import express from 'express';
import { renderToPipeableStream } from 'react-dom/server';
import {
  createPersistedStore,
  createServerDataComponent,
} from '@data-client/ssr';

const rootId = 'react-root';

const app = express();
app.get('/*', (req: any, res: any) => {
  const [ServerDataProvider, 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}
    >
      <ServerDataProvider>{children}</ServerDataProvider>
    </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 '@data-client/ssr';

const rootId = 'react-root';

awaitInitialData().then(initialState => {
  hydrateRoot(
    document.getElementById(rootId),
    <DataProvider initialState={initialState}>{children}</DataProvider>,
  );
});

API

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

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

createServerDataComponent(useReadyCacheState, id = 'data-client-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 = 'data-client-data') => Promise(State)

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

Keywords

front-end

FAQs

Package last updated on 25 Nov 2024

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

About

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.

  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc

U.S. Patent No. 12,346,443 & 12,314,394. Other pending.