@ssrx/renderer
v0.5.1
Published
The SSRx renderer establishes some patterns to hook into the lifecycle of streaming SSR apps in a framework/library agnostic way. It is client and server framework agnostic, so long as the server runtime supports web streams and AsyncLocalStorage (node 18
Downloads
1,498
Readme
@ssrx/renderer
The SSRx renderer establishes some patterns to hook into the lifecycle of streaming SSR apps in a framework/library agnostic way. It is client and server framework agnostic, so long as the server runtime supports web streams and AsyncLocalStorage (node 18+, bun, deno, cloudflare, vercel, etc). A handful of renderer plugins for common libraries are maintained in this repo.
See the react-router-kitchen-sink and remix-vite examples for a look at how everything can work together in practice.
Usage
@ssrx/renderer
exports a createApp
function that allows you to compose all the pieces necessary to render a SSR
streamed application.
An example with React (Solid works almost exactly the same):
// In this case we're using the `react` renderer, which simply wraps @ssrx/renderer with a react specific stream function
import { createApp } from '@ssrx/react';
import { assetsPlugin } from '@ssrx/renderer/assets';
export const { clientHandler, serverHandler, ctx } = createApp({
// Usually a router plugin will provide the appRenderer, but you can always provide your own if needed
appRenderer:
({ req }) =>
() =>
<div>My App</div>,
plugins: [
// If you are also using `@ssrx/vite`, this plugin automatically injects js/css assets into your html stream
assetsPlugin(),
// ... your plugins, or 3rd party plugins. More on the plugin shape below
],
});
import { hydrateRoot } from 'react-dom/client';
import { clientHandler } from './app.tsx';
void hydrate();
async function hydrate() {
const app = await clientHandler();
hydrateRoot(document, app());
}
import { serverHandler } from '~/app.tsx';
export default {
fetch(req: Request) {
const { stream, statusCode } = await serverHandler({ req });
return new Response(stream, { status: statusCode(), headers: { 'Content-Type': 'text/html' } });
},
};
With the above steps you get a streaming react app with support for lazy asset preloading. However, plugins are where
@ssrx/renderer
really shines.
Plugins
Plugins can:
- Hook into the client and server rendering in a standardized way
- Extend a typesafe
ctx
object that is made available on the client and the server, even outside of the rendering tree (for example in router loader functions). This is accomplished via a proxy that is exposed on the window in the client context, and via async local storage on the server.
Plugin Shape
See the renderer types file for the full plugin signature.
export type RenderPlugin = {
id: string;
/**
* Called once per request.
*/
hooksForReq: (props: {
req: Request;
meta?: SSRx.ReqMeta;
renderProps: SSRx.RenderProps;
ctx: Record<string, unknown>;
}) => {
// Called on the client and the server
common?: {
/**
* Extend the app ctx object with additional properties. Consider this "external" context - it is made available
* to the end application on the server and the client.
*/
extendAppCtx?: () => Record<string, unknown>;
/**
* Wrap the app component with a higher-order component. This is useful for wrapping the app with providers, etc.
*/
wrapApp?: (props: { children: () => Config['jsxElement'] }) => Config['jsxElement'];
/**
* Render the final inner-most app component. Only one plugin may do this - usually a routing plugin.
*/
renderApp?: () => (() => Config['jsxElement']) | Promise<() => Config['jsxElement']>;
};
// Only called on the server
server?: {
/**
* Return a string to emit some HTML into the SSR stream just before the document's closing </head> tag.
*
* Triggers once per request.
*/
emitToDocumentHead?: Promise<string | undefined> | string | undefined;
/**
* Return a string to emit into the SSR stream just before the rendering
* framework (react, solid, etc) emits a chunk of the page.
*
* Triggers one or more times per request.
*/
emitBeforeStreamChunk?: Promise<string | undefined> | string | undefined;
/**
* Return a string to emit some HTML to the document body, after the client renderer's first flush.
*
* Triggers once per request.
*/
emitToDocumentBody?: Promise<string | undefined> | string | undefined;
/**
* Runs when the stream is done processing.
*/
onStreamComplete?: Promise<void> | void;
};
};
};
Directory
| Package | Release Notes | | ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | @ssrx/renderer | | | @ssrx/react | | | @ssrx/remix | | | @ssrx/solid | | | @ssrx/streaming | | | @ssrx/trpc-react-query | | | @ssrx/plugin-react-router | | | @ssrx/plugin-solid-router | | | @ssrx/plugin-tanstack-query | | | @ssrx/plugin-tanstack-router | | | @ssrx/plugin-trpc-react | | | @ssrx/plugin-unhead | |