SSR is the default render mode: every request produces fresh HTML on a Lambda invocation. Pick SSR when content is personalized, frequently changing, or genuinely dynamic per request.
Declaring an SSR Face
import { createFaceApp, type FaceModule } from '@theory-cloud/facetheory';
const faces: FaceModule[] = [
{
route: '/',
mode: 'ssr',
render: async () => ({
html: '<h1>Hello FaceTheory</h1>',
}),
},
];
export const app = createFaceApp({ faces });
mode: 'ssr' is one of three valid FaceMode values ('ssr' | 'ssg' | 'isr'). SPA navigation is not a FaceMode — see SPA navigation.
Adapter helpers
Most consumers use an adapter helper rather than building FaceRenderResult by hand:
- React:
createReactStreamFace/createReactFace - Vue:
createVueFace - Svelte:
createSvelteFace
Lambda Function URL streaming
Expose the app via Lambda Function URL streaming:
import { createLambdaUrlStreamingHandler } from '@theory-cloud/facetheory';
import { app } from './app.js';
export const handler = createLambdaUrlStreamingHandler({ app });
The handler expects Lambda’s awslambda.streamifyResponse global at runtime. For local tests, call handleLambdaUrlEvent(app, event) with a synthesized event — see Testing Guide.
What SSR guarantees
- Every request renders fresh HTML (no caching beyond what the request handler explicitly opts into via
headers). - The
renderfunction receives aFaceContextwith the request, route params, and proxy hint, plus any data returned by an optionalload(ctx)step. - Output is deterministic if the consumer keeps it so — see Deterministic head emission and FaceTheory’s stewardship posture on determinism.
When SSR is wrong
- Content that genuinely never changes between deploys → use SSG.
- Content that changes on a schedule, not per-request → use blocking ISR backed by TableTheory.
- An application shell whose interactivity dwarfs its initial render → consider SPA navigation on top of SSR.