This guide walks through migrating an existing Litro project from one adapter to another. The adapter is a per-project choice — you pick one framework for the entire project.
Elena is deprecated and removed in Litro v1. The two sections that switch to Elena are kept for the projects already on it, and read in reverse they are the route off it. A new switch should land on Lit or FAST.
When switching adapters, you need to update:
app.ts — different hydration/runtime importsnitro.config.ts — different externals and esbuild settingspackage.json — different framework dependenciesWhat does not change:
server/api/ handlersdefinePageData() exportsroutes.generated.ts (auto-generated)litro.recipe.json / content directorylitro build, litro dev, litro preview)pnpm remove lit @lit-labs/ssr @lit-labs/ssr-client
pnpm add @microsoft/fast-element @microsoft/fast-ssr
app.ts// Before (Lit)
import '@lit-labs/ssr-client/lit-element-hydrate-support.js';
import '@beatzball/litro/runtime/LitroOutlet.js';
import '@beatzball/litro/runtime/LitroLink.js';
// After (FAST)
import '@microsoft/fast-element/install-element-hydration.js';
import '@beatzball/litro/adapter/fast/runtime';
// Before (Lit)
import { html } from 'lit';
import { customElement } from 'lit/decorators.js';
import { LitroPage } from '@beatzball/litro/runtime';
@customElement('page-home')
export class HomePage extends LitroPage {
render() {
const data = this.serverData as { message: string } | null;
return html`<h1>${data?.message ?? 'Loading...'}</h1>`;
}
}
// After (FAST)
import { observable, html, css } from '@microsoft/fast-element';
import { LitroPage } from '@beatzball/litro/adapter/fast/page';
export class HomePage extends LitroPage {
@observable override serverData: { message: string } | null = null;
}
HomePage.define({
name: 'page-home',
template: html<HomePage>`
<h1>${x => x.serverData?.message ?? 'Loading...'}</h1>
`,
});
nitro.config.tsRemove Lit-specific externals and esbuild settings:
// Remove these Lit-specific settings:
externals: { inline: ['@lit-labs/ssr', '@lit-labs/ssr-client'] },
esbuild: {
options: {
tsconfigRaw: {
compilerOptions: {
experimentalDecorators: true,
useDefineForClassFields: false,
},
},
},
},
FAST packages stay external by default (no externals config needed).
Switching TO Elena is not recommended. The adapter is deprecated and removed in Litro v1. These steps stay here because they are also the map for reading a switch in reverse — an Elena project moving to Lit reverses every one of them.
pnpm remove lit @lit-labs/ssr @lit-labs/ssr-client
pnpm add @elenajs/core
app.ts// Before (Lit)
import '@lit-labs/ssr-client/lit-element-hydrate-support.js';
import '@beatzball/litro/runtime/LitroOutlet.js';
import '@beatzball/litro/runtime/LitroLink.js';
// After (Elena)
import '@beatzball/litro/adapter/elena/runtime';
// Before (Lit)
import { html, css } from 'lit';
import { customElement } from 'lit/decorators.js';
import { LitroPage } from '@beatzball/litro/runtime';
@customElement('page-home')
export class HomePage extends LitroPage {
static styles = css`:host { display: block; }`;
render() {
const data = this.serverData as { message: string } | null;
return html`<h1>${data?.message ?? 'Loading...'}</h1>`;
}
}
// After (Elena)
import { html } from '@elenajs/core';
import { LitroPage } from '@beatzball/litro/adapter/elena/page';
export class HomePage extends LitroPage {
static override tagName = 'page-home';
render() {
const data = this.serverData as { message: string } | null;
return html`<h1>${data?.message ?? 'Loading...'}</h1>`;
}
}
HomePage.define();
@scopeShadow DOM styles become @scope rules in your global stylesheet:
/* Before: inside the component (Shadow DOM) */
static styles = css`
:host { display: block; padding: 2rem; }
h1 { color: #1a1a2e; }
`;
/* After: in a global CSS file */
@scope (page-home) {
:scope { display: block; padding: 2rem; }
h1 { color: #1a1a2e; }
}
nitro.config.tsRemove all Lit-specific settings (externals, esbuild decorator config). Elena has no special Nitro requirements.
Same caveat: Elena is deprecated and removed in v1. Read this section in reverse to move an Elena project onto FAST.
Follow the same pattern as Lit to Elena, but replace FAST-specific code:
@microsoft/fast-element imports become @elenajs/coreComponentClass.define({ name, template, styles }) becomes static tagName + render() method + .define()${x => x.prop} to ${this.prop}@observable decorators, use Elena's static propsunsafeHTML() in Lit, use Elena's unsafeHTML() from @elenajs/core@observable and jiti — if your Lit pages used @property with SSR, switch to Observable.defineProperty() in FAST (jiti cannot process @observable)definePageData stays unchanged — it's adapter-agnostic, don't touch itfetchData() stays unchanged — same method signature across all adapters