|
| 1 | +--- |
| 2 | +title: Lit SSR client usage |
| 3 | +eleventyNavigation: |
| 4 | + key: Client usage |
| 5 | + parent: Server rendering |
| 6 | + order: 3 |
| 7 | +--- |
| 8 | + |
| 9 | +{% labs-disclaimer %} |
| 10 | + |
| 11 | +Lit SSR generates static HTML for the browser to parse and paint without any JavaScript. (Browsers that do not have support for Declarative Shadow DOM will require some JavaScript polyfill for Lit components authored to utilize the Shadow DOM.) For pages with static content, this is all that's needed. However, if the page content needs to be dynamic and respond to user interactions, it will need JavaScript to re-apply that reactivity. |
| 12 | + |
| 13 | +How to re-apply that reactivity client-side depends on whether you are rendering standalone Lit templates or utilizing Lit components. |
| 14 | + |
| 15 | +## Standalone Lit Templates |
| 16 | +"Hydration" for Lit templates is the process of having Lit re-associate the expressions of Lit templates with the nodes they should update in the DOM as well as adding event listeners. In order to hydrate Lit templates, the `hydrate()` method from the `experimental-hydrate` module is provided in the `lit` package. Before you update 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: |
| 17 | + |
| 18 | +```js |
| 19 | +import {render} from 'lit'; |
| 20 | +import {hydrate} from 'lit/experimental-hydrate.js'; |
| 21 | +import {myTemplate} from './my-template.js'; |
| 22 | +// Initial hydration required before render: |
| 23 | +// (must be same data used to render on the server) |
| 24 | +const initialData = getInitialAppData(); |
| 25 | +hydrate(myTemplate(initialData), document.body); |
| 26 | + |
| 27 | +// After hydration, render will efficiently update the server-rendered DOM: |
| 28 | +const update = (data) => render(myTemplate(data), document.body); |
| 29 | +``` |
| 30 | + |
| 31 | +## Lit components |
| 32 | +To re-apply reactivity to Lit components, custom element definitions need to be loaded for them to upgrade, enabling their lifecycle callbacks, and the templates in the components' shadow roots needs to be hydrated. |
| 33 | + |
| 34 | +Upgrading can be achieved simply by loading the component module that registers the custom element. This can be done by loading a bundle of all the component definitions for a page, or may be done based on more sophisticated heuristics where only a subset of definitions are loaded as needed. To ensure templates in `LitElement` shadow roots are hydrated, load the `lit/experimental-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 (including any component modules that import `lit`) to ensure hydration support is properly installed. |
| 35 | + |
| 36 | +When Lit components are server rendered, their shadow root contents are emitted inside a `<template shadowroot>`, also known as a [Declarative Shadow Root](https://web.dev/declarative-shadow-dom/). Declarative shadow roots automatically attach their contents to a shadow root on the template's parent element when HTML is parsed without the need for JavaScript. |
| 37 | + |
| 38 | +Until all browsers include declarative shadow DOM support, a very small polyfill is available that can be inlined into your page. This lets you use SSR now for any browsers with JavaScript enabled and incrementally address non-JavaScript use cases as the feature is rolled out to other browsers. The usage of the [`template-shadowroot` polyfill](https://github.com/webcomponents/template-shadowroot) is described below. |
| 39 | + |
| 40 | +### Loading `lit/experimental-hydrate-support.js` |
| 41 | +This needs to be loaded before any component modules and the `lit` library. |
| 42 | + |
| 43 | +For example: |
| 44 | +```html |
| 45 | + <body> |
| 46 | + <!-- App components rendered with declarative shadow DOM placed here. --> |
| 47 | + |
| 48 | + <!-- exprimental-hydrate-support should be loaded first. --> |
| 49 | + <script src="/node_modules/lit/exprimental-hydrate-support.js"></script> |
| 50 | + |
| 51 | + <!-- As compnent definition loads, your pre-rendered components will |
| 52 | + come to life and become interactive. --> |
| 53 | + <script src="/app-components.js"></script> |
| 54 | + </body> |
| 55 | +``` |
| 56 | + |
| 57 | +If you are [bundling](/docs/tools/production/) your code, make sure the `lit/expriemntal-hydrate-support.js` is imported first: |
| 58 | +```js |
| 59 | +// index.js |
| 60 | +import 'lit/experimental-hydrate-support.js'; |
| 61 | +import './app-components.js'; |
| 62 | +``` |
| 63 | + |
| 64 | +### Using the `template-shadowroot` polyfill |
| 65 | +The HTML snippet below includes an optional strategy to hide the body until the polyfill is loaded to prevent layout shifts. |
| 66 | + |
| 67 | +```html |
| 68 | +<!DOCTYPE html> |
| 69 | +<html> |
| 70 | + <head> |
| 71 | + <!-- On browsers that don't yet support native declarative shadow DOM, a |
| 72 | + paint can occur after some or all pre-rendered HTML has been parsed, |
| 73 | + but before the declarative shadow DOM polyfill has taken effect. This |
| 74 | + paint is undesirable because it won't include any component shadow DOM. |
| 75 | + To prevent layout shifts that can result from this render, we use a |
| 76 | + "dsd-pending" attribute to ensure we only paint after we know |
| 77 | + shadow DOM is active. --> |
| 78 | + <style> |
| 79 | + body[dsd-pending] { |
| 80 | + display: none; |
| 81 | + } |
| 82 | + </style> |
| 83 | + </head> |
| 84 | + |
| 85 | + <body dsd-pending> |
| 86 | + <script> |
| 87 | + if (HTMLTemplateElement.prototype.hasOwnProperty('shadowRoot')) { |
| 88 | + // This browser has native declarative shadow DOM support, so we can |
| 89 | + // allow painting immediately. |
| 90 | + document.body.removeAttribute('dsd-pending'); |
| 91 | + } |
| 92 | + </script> |
| 93 | + |
| 94 | + <!-- App components rendered with declarative shadow DOM placed here. --> |
| 95 | + |
| 96 | + <!-- Use a type=module script so that we can use dynamic module imports. |
| 97 | + Note this pattern will not work in IE11. --> |
| 98 | + <script type="module"> |
| 99 | + // Check if we require the template shadow root polyfill. |
| 100 | + if (!HTMLTemplateElement.prototype.hasOwnProperty('shadowRoot')) { |
| 101 | + // Fetch the template shadow root polyfill. |
| 102 | + const {hydrateShadowRoots} = await import( |
| 103 | + '/node_modules/@webcomponents/template-shadowroot/template-shadowroot.js' |
| 104 | + ); |
| 105 | +
|
| 106 | + // Apply the polyfill. This is a one-shot operation, so it is important |
| 107 | + // it happens after all HTML has been parsed. |
| 108 | + hydrateShadowRoots(document.body); |
| 109 | +
|
| 110 | + // At this point, browsers without native declarative shadow DOM |
| 111 | + // support can paint the initial state of your components! |
| 112 | + document.body.removeAttribute('dsd-pending'); |
| 113 | + } |
| 114 | + </script> |
| 115 | + </body> |
| 116 | +</html> |
| 117 | +``` |
| 118 | + |
| 119 | +### Combined example |
| 120 | +This example shows a strategy that combines both the `lit/experimental-hydrate-support.js` and the `template-shadowroot` polyfill loading and serves a page with a SSRed component to hydrate client-side. |
| 121 | + |
| 122 | +[Lit SSR in a Koa server](https://stackblitz.com/edit/lit-ssr-global?file=src/server.js) |
0 commit comments