SSR for Lit
npm install @lit-labs/ssrA package for server-side rendering Lit templates and components.
> [!WARNING]
>
> This package is part of Lit Labs. It
> is published in order to get feedback on the design and may receive breaking
> changes or stop being supported.
>
> Please read our Lit Labs documentation
> before using this library in production.
>
> Documentation: https://lit.dev/docs/ssr/overview/
>
> Give feedback: https://github.com/lit/lit/discussions/3353
@lit-labs/ssr is pre-release software, not quite ready for public consumption.
If you try Lit SSR, please give feedback and file issues with bugs and use cases
you'd like to see covered.
The easiest way to get started is to import your Lit template modules (and anyLitElement definitions they may use) into the node global scope and render
them to a stream (or string) using the render(value: unknown, renderInfo?: Partial function provided by @lit-labs/ssr. When
running in Node, Lit automatically depends on Node-compatible implementations of
a minimal set of DOM APIs provided by the @lit-labs/ssr-dom-shim package,
including defining customElements on the global object.
#### Rendering to a stream
Web servers should prefer rendering to a stream, as they have a lower memory
footprint and allow sending data in chunks as they are being processed. For this
case use RenderResultReadable, which is a Node Readable stream
implementation that provides values from RenderResult. This can be piped
into a Writable stream, or passed to web server frameworks like Koa.
``js
// Example: server.js:
import {renderThunked} from '@lit-labs/ssr';
import {RenderResultReadable} from '@lit-labs/ssr/lib/render-result-readable.js';
import {myTemplate} from './my-template.js';
//...
const ssrResult = renderThunked(myTemplate(data));
// Assume context is a Koa.Context.`
context.body = new RenderResultReadable(ssrResult);
#### Rendering to a string
To render to a string, you can use the collectResult or collectResultSync helper functions.
`js
import {renderThunked} from '@lit-labs/ssr';
import {
collectResult,
collectResultSync,
} from '@lit-labs/ssr/lib/render-result.js';
import {html} from 'lit';
const myServerTemplate = (name) => html
Hello ${name}
;
const ssrResult = renderThunked(myServerTemplate('SSR with Lit!'));// Will throw if a Promise is encountered
console.log(collectResultSync(ssrResult));
// Awaits promises
console.log(await collectResult(ssrResult));
`$3
To avoid polluting the Node.js global object with the DOM shim and ensure each request receives a fresh global object, we also provide a way to load app code into, and render from, a separate VM context with its own global object. _Note that using this feature requires Node 14+ and passing the
--experimental-vm-modules flag to node on because of its use of experimental VM modules for creating a module-compatible VM context._To render in a VM context, the
renderModule function from the
render-module.js module will import a given module into a server-side VM
context shimmed with the minimal DOM shim required for Lit server rendering,
execute a given function exported from that module, and return its value.Within that module, you can call the
render method from the
render-lit-html.js module to render lit-html templates and return an
iterable that incrementally emits the serialized strings of the given template.`js
// Example: render-template.jsimport {renderThunked} from '@lit-labs/ssr';
import {myTemplate} from './my-template.js';
export const renderTemplate = (someData) => {
return renderThunked(myTemplate(someData));
};
``js
// Example: server.js:import {RenderResultReadable} from '@lit-labs/ssr/lib/render-result-readable.js';
import {renderModule} from '@lit-labs/ssr/lib/render-module.js';
// Execute the above
renderTemplate in a separate VM context with a minimal DOM shim
const ssrResult = await (renderModule(
'./render-template.js', // Module to load in VM context
import.meta.url, // Referrer URL for module
'renderTemplate', // Function to call
[{some: "data"}] // Arguments to function
) as Promise>);// ...
// Assume
context is a Koa.Context, or other API that accepts a Readable.
context.body = new RenderResultReadable(ssrResult);
`Client usage
$3
"Hydration" is the process of re-associating expressions in a template with the nodes they should update in the DOM. Hydration is performed by the
hydrate() function from the @lit-labs/ssr-client module.Prior to updating a server-rendered container using
render(), you must first call hydrate() on that container using the same template and data that was used to render on the server:`js
import {myTemplate} from './my-template.js';
import {render} from 'lit';
import {hydrate} from '@lit-labs/ssr-client';
// Initial hydration required before render:
// (must be same data used to render on the server)
const initialData = getInitialAppData();
hydrate(myTemplate(initialData), document.body);// After hydration, render will efficiently update the server-rendered DOM:
const update = (data) => render(myTemplate(data), document.body);
`$3
When
LitElements are server rendered, their shadow root contents are emitted inside a , also known as a Declarative Shadow Root, a new browser feature that shipped in all modern browsers. Declarative shadow roots automatically attach their contents to a shadow root on the template's parent element when parsed. For browsers that do not yet implement declarative shadow root, there is a template-shadowroot polyfill, described below.hydrate() does not descend into shadow roots - it only works on one scope of the DOM at a time. To hydrate LitElement shadow roots, load the @lit-labs/ssr-client/lit-element-hydrate-support.js module, which installs support for LitElement to automatically hydrate itself when it detects it was server-rendered with declarative shadow DOM. This module must be loaded before the lit module is loaded, to ensure hydration support is properly installed.Put together, an HTML page that was server rendered and containing
LitElements in the main document might look like this:`js
import {html} from 'lit';
import {render} from '@lit-labs/ssr';
import './app-components.js';const ssrResult = render(html
);
// ...
context.body = Readable.from(ssrResult);
`
Note that as a simple example, the code above assumes a static top-level
template that does not need to be hydrated on the client, and that top-level
components individually hydrate themselves using data supplied either by
attributes or via a side-channel mechanism. This is in no way fundamental; the
top-level template can be used to pass data to the top-level components, and
that template can be loaded and hydrated on the client to apply the same data.
@lit-labs/ssr also exports an html template function, similar to the normal Lit html function, only it's used for server-only templates. These templates can be used for rendering full documents, including the , and rendering into elements that Lit normally cannot, like
,