Litro's adapter system lets you choose which web component framework powers your project. All three adapters share the same infrastructure — file-based routing, definePageData(), LitroRouter, content layer, SSG, deployment adapters — only the component authoring model differs.
| Adapter | Framework | DOM Model | SSR Strategy | Hydration | CSS Scoping |
|---|---|---|---|---|---|
| lit (default) | Lit 3 | Shadow DOM | @lit-labs/ssr (DSD) | Yes | Shadow DOM |
| fast | FAST Element 2 | Shadow DOM | @microsoft/fast-ssr (DSD) | Yes | Shadow DOM |
| elena | Elena | Light DOM | Direct rendering | No (progressive enhancement) | @scope CSS |
Use Lit if you want the most mature web component library with the largest ecosystem, decorator-based APIs, and first-class TypeScript support. This is the default and most battle-tested option.
Use FAST Element if you prefer Microsoft's observable-based reactivity model, auto-generated attributes, or need compatibility with Fluent UI Web Components.
Use Elena if you want light DOM rendering — no Shadow DOM boundaries, global CSS reaches component internals, smaller HTML payloads, and no hydration overhead. Best for content-heavy sites where progressive enhancement is preferred over client-side hydration.
The --adapter flag selects the framework at project creation:
# Lit (default — can omit the flag)
pnpm create @beatzball/litro my-app --adapter lit
# FAST Element
pnpm create @beatzball/litro my-app --adapter fast
# Elena
pnpm create @beatzball/litro my-app --adapter elena
Or omit --adapter and choose interactively during scaffolding.
Regardless of which adapter you choose, these features work identically:
pages/ directory convention, dynamic segments, catch-all routesdefinePageData() — server-side data fetching, serialized into the HTML shellLitroRouter — client-side SPA navigation via <litro-link> and history.pushStatelitro:content virtual module for Markdownserver/api/ with H3 handlersgenerateRoutes() for static prerenderingEach adapter provides its own native implementations of three internal components:
| Component | Purpose |
|---|---|
| LitroOutlet | Mounts the router, serves as the container for route content |
| LitroLink | Intercepts clicks for SPA navigation, falls back to <a> |
| LitroPage | Reads __litro_data__ before first render, exposes serverData |
These are imported from adapter-specific paths — see each adapter's guide for details.
All three adapters SSR page content, but the completeness of the static HTML differs:
| Feature | Lit | FAST | Elena |
|---|---|---|---|
| Template bindings | Full | Full | Full |
| Conditional templates | Full | Full | Full |
| List rendering | Full | Full (repeat()) | Full |
| Raw HTML injection | Full (unsafeHTML) | Client-only (:innerHTML) | Full (unsafeHTML) |
| Nested component expansion | Full (DSD) | Full (DSD) | Full (recursive) |
| crawlLinks discovery | Full | Partial (misses :innerHTML links) | Full |
| Lighthouse measurability | SSG only | SSG only | SSG + SSR |
The main difference: FAST's :innerHTML property binding does not emit content during SSR, so raw HTML (comment trees, Markdown content) only appears after client-side hydration. This also means crawlLinks may miss <a> tags inside :innerHTML content. Use explicit prerender.routes when :innerHTML contains links.
Elena produces the smallest static HTML because it uses light DOM (no <template shadowrootmode> wrappers), while Lit produces the largest because DSD duplicates styles per shadow root instance. All three render full content for search engines in SSG mode, with the caveat above for FAST's :innerHTML.
Where an adapter limitation requires a workaround, it currently lives in the app, not the framework — users need to know what they'll own.
:innerHTML fill — the HN benchmark app fills :innerHTML content via a post-build Nitro compiled hook that reads the serialized __litro_data__ and replaces empty markers in the static HTML. This keeps the benchmark comparison fair, but the underlying adapter limitation still applies to any FAST project — teams must add their own post-build step (or avoid :innerHTML for SSG-critical content) until a framework-level fix lands.unsafeHTML() is the standard public API for raw HTML (not a workaround); Next.js's output: 'export' and Nuxt's prerender.routes are first-class static export config.Scope caveat: the HN benchmark is intentionally narrow — list pages, param routes, nested comments, no forms, no streaming SSR, no user input. "No workarounds needed" is a claim about this scope, not a proof of full adapter parity under every real-world workload.
Lighthouse note: Lighthouse cannot measure paint events inside shadow roots, so Lit and FAST SSR pages always score 0 on performance. Lighthouse scores are only meaningful for SSG output or Elena (light DOM). This is a Lighthouse limitation, not a rendering issue — the content is visible to users.
All three adapters are benchmarked with identical Hacker News clones (~100 pages each) alongside Next.js and Nuxt. Current benchmarks cover static prerendering (SSG) — build times, output sizes, and page weight comparisons. Per-request SSR benchmarks (TTFB, throughput, latency) are planned for a future update. See the benchmarks page for results.