

Hydrate/dehydration utilities for Server Side Rendering with the Reactive Data Client
Usage
Integration with
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
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => App,
enhanceComponent: (Component) => Component,
})
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) {
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() {
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);
},
},
);
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 />