Lit SSR server usage
Rendering templatesPermalink to “Rendering templates”
Server rendering begins with rendering a Lit template with a server-specific
render() function provided in the
The signature of the render function is:
value is a
TemplateResult produced by a Lit template expression, like:
The template can contain custom elements. If the custom elements are defined on the server, they'll be rendered in turn, along with their templates.
To render a single element, you render a template that only contains that element:
Handling RenderResultsPermalink to “Handling RenderResults”
render() returns a
RenderResult: an iterable of values that can be streamed or concatenated into a string.
RenderResult can contain strings, nested render results, or Promises of strings or render results. Not all render results contain Promises—those can occur when custom elements perform async tasks, like fetching data—but because a
RenderResult can contain Promises, processing it into a string or an HTTP response is potentially an async operation.
Even though a
RenderResult can contain Promises, it is still a sync iterable, not an async iterable. This is because sync iterables are faster than async iterables and many server renders will not require async rendering, and so shouldn't pay the overhead of an async iterable.
Allowing Promises in a sync iterable creates a kind of hybrid sync / async iteration protocol. When consuming a
RenderResult, you must check each value to see if it is a Promise or iterable and wait or recurse as needed.
@lit-labs/ssr contains three utilities to do this for you:
Permalink to “RenderResultReadable”
RenderResultReadable is a Node
Readable stream implementation that provides values from a
RenderResult. This can be piped into a
Writable stream, or passed to web server frameworks like Koa.
This is the preferred way to handle SSR results when integrating with a streaming HTTP server or other stream-supporting API.
Permalink to “collectResult()”
collectResult(result: RenderResult): Promise<string>
collectResult() is an async function that takes a
RenderResult and joins it into a string. It waits for Promises and recurses into nested iterables.
Permalink to “collectResultSync()”
collectResultSync(result: RenderResult): Promise<string>
collectResultSync() is a sync function that takes a
RenderResult and joins it into a string. It recurses into nested iterables, but throws when it encounters a Promise.
Because this function doesn't support async rendering, it's recommended to only use it when you can't await async functions.
Render optionsPermalink to “Render options”
The second argument to
render() is a
RenderInfo object that is used to pass options and current render state to components and sub-templates.
The main options that can be set by callers are:
deferHydration: controls whether the top-level custom elements have a
defer-hydrationattribute added to signal that the elements should not automatically hydrate. This defaults to
falseso that top-level elements do automatically hydrate.
elementRenderers: An array of
ElementRendererclasses to use for rendering custom elements. By default this contains
LitElementRendererto render Lit elements. It can be set to include custom
ElementRendererinstances (documentation forthcoming), or set to an empty array to disable custom element rendering altogether.
Running SSR in a VM module or the global scopePermalink to “Running SSR in a VM module or the global scope”
In order to render custom elements in Node, they must first be defined and registered with the global
customElements API, which is a browser-only feature. As such, when Lit runs in Node, it automatically uses a set of minimal DOM APIs necessary to render Lit on the server, and defines the
customElements global. (For a list of emulated APIs, see DOM emulation.)
Lit SSR provides two different ways of rendering custom elements server-side: rendering in the global scope or via VM modules. VM modules utilizes Node's
vm.Module API, which enables running code within V8 Virtual Machine contexts. The two methods differ primarily in how global state, such as the custom elements registry, are shared.
When rendering in the global scope, a single shared
customElements registry will be defined and shared across all render requests, along with any other global state that your component code might set.
Rendering with VM modules allows each render request to have its own context with a separate global from the main Node process. The
customElements registry will only be installed within that context, and other global state will also be isolated to that context. VM modules are an experimental Node feature.
Global ScopePermalink to “Global Scope”
When using the global scope, you can just call
render() with a template to get a
RenderResult and pass that to your server:
VM ModulePermalink to “VM Module”
Lit also provide a way to load application code into, and render from, a separate VM context with its own global object.
Note: Using this feature requires Node 14+ and passing the
--experimental-vm-modules flag to Node because of its use of experimental VM modules for creating a module-compatible VM context.