@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 ResponseMinimal Setup
Section titled “Minimal Setup”pnpm add @lentystyle/ssrnpm install @lentystyle/ssryarn add @lentystyle/ssrZero-Config Runner
Section titled “Zero-Config Runner”The fastest way in: build your site, then run the bundled CLI over the output.
pnpm add @lentystyle/ssrnpx lenty-ssrWith no config file the runner uses conventions:
- reads built html from
./dist - auto-discovers inline and linked
.luissources 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: {},})What SSR Does
Section titled “What SSR Does”- Takes a route HTML snapshot
- Compiles the
.luissource 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
What SSR Does Not Do
Section titled “What SSR Does Not Do”- 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-ssrrunner does auto-discover) - Does not provide distributed cache or persistent storage
Guard Profile
Section titled “Guard Profile”The SSR request flow uses @lentystyle/core/guard/ssr.
This adapter:
- uses the
strictpreset 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
Default guard flow
Section titled “Default guard flow”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'
When custom guard behavior is needed
Section titled “When custom guard behavior is needed”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) // 0Recommended Starting Point
Section titled “Recommended Starting Point”- Generate the route HTML snapshot
- Prepare the
.luissource list - Choose the right SSR surface
- Write the
result.htmloutput to the response
Available surfaces:
renderLentySsrSnapshot()→ one-time rendercreateLentySsrIntegration()→ reusable direct surfacecreateLentySsrFromProjectConfig()→ config-drivenrenderRoute()with route-to-source mappingscreateLentySsrProdFromProjectConfig()→ the same surface with the process-local render cachecreateLentySsrFrameworkAdapter()→ optional adapter seamcreateLentySsrProd()→ helper with process-local cache supportrunLentySsrProjectRender()→ programmatic equivalent of thelenty-ssrCLI render passverifyLentySsrProjectOutput()→ programmatic equivalent of the CLI's--verifypass
Quick Example
Section titled “Quick Example”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.csscontains the generated CSSresult.runtimePayloadcontains the hybrid route payloadresult.htmlcontains the final injected HTML
When luis.config.mjs Matters
Section titled “When luis.config.mjs Matters”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'], },})Runtime and Guard Separation Inside SSR
Section titled “Runtime and Guard Separation Inside SSR”SSR also keeps convenience and hardening roles separate:
- the browser runtime stays optional and convenience-oriented on the client side
@lentystyle/core/guard/ssrhardens 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.
Errors
Section titled “Errors”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' }}| Code | Meaning |
|---|---|
guard-rejected | request html or source envelope failed guard validation |
compile-failed | the underlying hybrid compile step failed |
adapter-callback-failed | an adapter contract callback (resolveRouteId, renderHtml, resolveSources, finalizeHtml) threw |
project-config-invalid | the luis.config.mjs ssr block or a routes mapping is invalid; error.routeId is set when the failure is tied to a specific route |
Next Steps
Section titled “Next Steps”Configfor request-level and project-level settingsFrameworksfor Express, Astro, and Vite request-time recipesBrowser Runtimefor browser-side convenience behavior@lentystyle/guarddocs for the fail-closed validation model