SSR micro-server that pairs with the universal_renderer
Ruby gem.
• Express-based – lightweight and familiar Node.js server. • Framework-agnostic – just start a server and hand it JSX/HTML. • Simple API – minimal configuration, maximum flexibility.
npm install universal-renderer
// ssr.ts
import { createServer } from "universal-renderer";
import { renderToString } from "react-dom/server.node";
import App from "./App";
const app = await createServer({
port: 3001,
setup: async (url, props) => {
// Set up your app context - routing, state, etc.
return {
jsx: <App {...props} />,
url,
props
};
},
render: async (context) => {
// Render your React app to HTML
const html = renderToString(context.jsx);
return {
head: '<meta name="description" content="SSR App">',
body: html,
bodyAttrs: 'class="ssr-rendered"'
};
},
cleanup: (context) => {
// Clean up any resources if needed
console.log(`Rendered ${context.url}`);
}
});
app.listen(3001, () => {
console.log("SSR server running on http://localhost:3001");
});
import { createServer } from "universal-renderer";
import { renderToPipeableStream } from "react-dom/server.node";
const app = await createServer({
port: 3001,
setup: async (url, props) => ({ url, props }),
render: async (context) => ({ body: "fallback" }), // Required but not used for streaming
streamCallbacks: {
node: (context) => context.app,
head: async (context) => {
// Generate dynamic head content
return `<meta name="description" content="Page: ${context.url}">`;
},
},
});
Point the gem at http://localhost:3001
and you're done.
Creates an Express application configured for SSR. Each request arrives as { url, props }
JSON and must respond with:
export type RenderOutput = {
head?: string; // <head> inner HTML
body: string; // rendered markup (required)
bodyAttrs?: string; // optional attributes for <body>
};
The library exports marker constants for template placeholders:
import { SSR_MARKERS } from "universal-renderer";
// Available markers:
SSR_MARKERS.HEAD; // "<!-- SSR_HEAD -->"
SSR_MARKERS.BODY; // "<!-- SSR_BODY -->"
These markers are used by the Rails gem to inject SSR content into your templates.
-
setup(url, props)
→context
— prepare your app context. -
render(context)
→RenderOutput
— stringify markup. -
cleanup(context)
(optional) — dispose per-request resources. -
streamCallbacks
(optional) — for streaming SSR support. -
middleware
(optional) — Express middleware for static assets, etc.
For streaming SSR, provide streamCallbacks
:
streamCallbacks: {
node: (context) => <YourReactApp />,
head?: (context) => "<meta name='description' content='...' />",
transform?: (context) => someTransformStream
}
For a full Rails + React walk-through, see the root repo README.