Litro renders all pages on the server before sending HTML to the browser. The SSR strategy depends on which adapter your project uses:
@lit-labs/ssr or @microsoft/fast-ssr. DSD is a browser-native way to express shadow roots in HTML — no JavaScript required to parse the initial structure.render(), and stringifies the result.The SSR output streams directly to the browser as it's generated. This means the browser can start parsing and rendering the top of the page before the server finishes rendering the bottom.
Add @lit-labs/ssr-client/lit-element-hydrate-support.js as the first import in your app.ts. This patches LitElement to support DSD hydration:
// app.ts
import '@lit-labs/ssr-client/lit-element-hydrate-support.js'; // MUST be first
import '@beatzball/litro/runtime/LitroOutlet.js';
// ...
Components that access window, document, or localStorage at module evaluation time will throw during SSR. Guard such access:
// Safe — only runs in the browser
override firstUpdated() {
if (typeof localStorage !== 'undefined') {
this._theme = localStorage.getItem('theme') ?? 'light';
}
}
For components that genuinely cannot be made SSR-safe, move the browser-only work into a lifecycle hook (connectedCallback, firstUpdated) or behind a typeof window !== 'undefined' guard, and dynamic-import() any browser-only modules from inside that hook so they never load on the server. If a component still throws during SSR, Litro's page handler logs the error and falls back to serving the client-only HTML shell — the page remains usable, but you lose the SSR benefits.