@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):
src/app.tsx
import { createApp } from '@ssrx/react';
import { assetsPlugin } from '@ssrx/renderer/assets';
export const { clientHandler, serverHandler, ctx } = createApp({
appRenderer:
({ req }) =>
() =>
<div>My App</div>,
plugins: [
assetsPlugin(),
],
});
src/entry.client.tsx
import { hydrateRoot } from 'react-dom/client';
import { clientHandler } from './app.tsx';
void hydrate();
async function hydrate() {
const app = await clientHandler();
hydrateRoot(document, app());
}
src/server.ts
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;
hooksForReq: (props: {
req: Request;
meta?: SSRx.ReqMeta;
renderProps: SSRx.RenderProps;
ctx: Record<string, unknown>;
}) => {
common?: {
extendAppCtx?: () => Record<string, unknown>;
wrapApp?: (props: { children: () => Config['jsxElement'] }) => Config['jsxElement'];
renderApp?: () => (() => Config['jsxElement']) | Promise<() => Config['jsxElement']>;
};
server?: {
emitToDocumentHead?: Promise<string | undefined> | string | undefined;
emitBeforeStreamChunk?: Promise<string | undefined> | string | undefined;
emitToDocumentBody?: Promise<string | undefined> | string | undefined;
onStreamComplete?: Promise<void> | void;
};
};
};
Directory