Skip to content

SSR Overview

@lentystyle/ssr is the SSR orchestration layer that injects CSS and runtime payload into a route HTML snapshot. It does not render framework HTML itself; it consumes an existing HTML snapshot and the .luis source list for that route.

Compiler -> Hybrid -> SSR -> HTML Response

Terminal window
pnpm add @lentystyle/ssr

The fastest way in: build your site, then run the bundled CLI over the output.

Terminal window
pnpm add @lentystyle/ssr
npx lenty-ssr

With no config file the runner uses conventions:

  • reads built html from ./dist
  • auto-discovers inline and linked .luis sources per page
  • renders every page through the cache-backed SSR pipeline
  • writes the result into ./dist-ssr

Useful flags: --dry-run (list routes without writing), --verify (check payload artifacts, with optional --expect-route /docs/), --in-place, --html-dir, --out, --config.

If a luis.config.mjs with an ssr block exists, the runner reads it automatically; every field stays optional:

import { defineLuisConfig } from '@lentystyle/core'
export default defineLuisConfig({
ssr: {},
})

  • Takes a route HTML snapshot
  • Compiles the .luis source list for that route
  • Injects the generated CSS into the HTML
  • Writes the route payload as <script type="application/json">
  • Provides adapter seams for framework or server integration
  • Offers optional in-memory cache for repeated renders in the same process
  • Validates request HTML and source envelope inputs in strict mode with @lentystyle/core/guard/ssr
  • Does not render framework HTML for you
  • Does not start the browser runtime
  • Does not auto-discover route sources in the request-time API (the lenty-ssr runner does auto-discover)
  • Does not provide distributed cache or persistent storage

The SSR request flow uses @lentystyle/core/guard/ssr.

This adapter:

  • uses the strict preset by default
  • scans html with the configured htmlGuardMode (default 'ssr'; the runner defaults to 'hybrid')
  • keeps the LentyStyle SSR payload and bootstrap allowlist strictly SSR-only in 'ssr' mode
  • blocks invalid request HTML or source envelope inputs before hybrid compilation starts

In high-level SSR helpers you do not enable guard separately; it runs inside the request render flow.

import { renderLentySsrSnapshot } from '@lentystyle/ssr'
await renderLentySsrSnapshot({}, {
routeId: '/docs/',
html: '<html><head></head><body><div class="card"></div></body></html>',
sources: [
{
sourceId: 'docs.luis',
source: '.card { color: #0f172a; }',
},
],
})

The built-in behavior is fixed:

  • preset: 'strict'
  • htmlMode: 'ssr'

The public renderLentySsrSnapshot() and createLentySsrIntegration() surfaces do not directly accept guardPolicy. If custom validation is needed, you must run guard separately one layer above SSR or drop to lower-level orchestration.

import { scanSnapshotEnvelope } from '@lentystyle/core/guard/ssr'
const findings = scanSnapshotEnvelope(
{
routeId: '/docs/',
html: '<html><head></head><body><div class="card"></div></body></html>',
sources: [
{
sourceId: 'docs.luis',
source: '.card { color: #0f172a; }',
},
],
},
{ preset: 'strict', htmlMode: 'ssr' },
)
console.log(findings.length) // 0

  1. Generate the route HTML snapshot
  2. Prepare the .luis source list
  3. Choose the right SSR surface
  4. Write the result.html output to the response

Available surfaces:

  • renderLentySsrSnapshot() → one-time render
  • createLentySsrIntegration() → reusable direct surface
  • createLentySsrFromProjectConfig() → config-driven renderRoute() with route-to-source mappings
  • createLentySsrProdFromProjectConfig() → the same surface with the process-local render cache
  • createLentySsrFrameworkAdapter() → optional adapter seam
  • createLentySsrProd() → helper with process-local cache support
  • runLentySsrProjectRender() → programmatic equivalent of the lenty-ssr CLI render pass
  • verifyLentySsrProjectOutput() → programmatic equivalent of the CLI's --verify pass

import { renderLentySsrSnapshot } from '@lentystyle/ssr'
const result = await renderLentySsrSnapshot(
{ mode: 'ssr' },
{
routeId: '/docs/',
html: '<html><head></head><body><div class="card"></div></body></html>',
sources: [
{
sourceId: 'site.luis',
source: '.card { color: red; }',
},
],
},
)

After the call:

  • result.css contains the generated CSS
  • result.runtimePayload contains the hybrid route payload
  • result.html contains the final injected HTML

Low-level SSR APIs are object-based and do not require luis.config.mjs. But in app-level preview and project helper flows it is more useful to keep common SSR settings in the same config file.

import { defineLuisConfig } from '@lentystyle/core'
export default defineLuisConfig({
ssr: {
mode: 'ssr',
outDirName: 'dist-ssr',
defaultRuntimeOptions: '',
include: ['**/*.html'],
},
})

SSR also keeps convenience and hardening roles separate:

  • the browser runtime stays optional and convenience-oriented on the client side
  • @lentystyle/core/guard/ssr hardens the incoming HTML snapshot and source envelope input on the server side

Thanks to this separation, SSR can stay strict about payload and bootstrap HTML while the browser runtime keeps its own standalone policy model.


Every SSR failure rejects with a coded LentySsrError:

import { isLentySsrError } from '@lentystyle/ssr'
try {
await integration.renderRequest(input)
} catch (error) {
if (isLentySsrError(error)) {
console.log(error.code) // 'guard-rejected' | 'compile-failed' | 'adapter-callback-failed' | 'project-config-invalid'
}
}
CodeMeaning
guard-rejectedrequest html or source envelope failed guard validation
compile-failedthe underlying hybrid compile step failed
adapter-callback-failedan adapter contract callback (resolveRouteId, renderHtml, resolveSources, finalizeHtml) threw
project-config-invalidthe luis.config.mjs ssr block or a routes mapping is invalid; error.routeId is set when the failure is tied to a specific route

  • Config for request-level and project-level settings
  • Frameworks for Express, Astro, and Vite request-time recipes
  • Browser Runtime for browser-side convenience behavior
  • @lentystyle/guard docs for the fail-closed validation model