Web Components and SSR - 2024 Edition
Burton Smith
Posted on November 19, 2024
One of the biggest gripes I hear about web components is that they don't work with Server-Side Rendering. This is unfortunately a little misleading because it's only partially true. You see, there's server-side rendering and there's server-side rendering. It works in the former, but not the latter. Don't worry, I'll explain. 😉
Server-Side Rendering
There are really two kinds of server-side rendering that get conflated into the same concept. The first is server-side framework server-side rendering and client-side framework server-side rendering.
Server-Side Framework SSR
Server-side Framework SSR is when you use a framework that runs the HTML templating logic entirely on the server to compose the HTML that will be rendered in the browser. These are frameworks like Ruby on Rails, ASP.Net, PHP, or even Node.js frameworks that use templating languages like Pug or EJS.
Web components work great in this type of server-side rendering!
Client-Side Framework SSR
Client-side Framework SSR is when a frontend framework executes the client-side code on a Node.js server before sending it to the browser. This is done to reduce the amount of JavaScript that is shipped and executed in the browser.
There are some big advantages of doing this:
- improve performance
- better SEO
- reduced dependency on JavaScript
The Problem
The problem is that this approach introduces some challenges for custom elements and other web APIs.
The first challenge is that when defining custom elements the API is window.customElements.define('my-element', MyElement)
. When you render client-side code on a Node.js server, guess what's missing - the window
. Because of this, typical commands on the window
object, and other APIs like localStorage
and MutationObserver
fail during SSRing.
The other challenge is that web components are interoperable, which means they can be used across frameworks like standard HTML elements, but client-side framework SSRing is not based on any standard. Each framework has its own bespoke method of executing its UI, so there is no clear approach for how and when to SSR custom elements. You need an implementation for each environment in which they are being used.
Current Solutions
You can do some things right now to use your web components with client-side framework SSRing.
Deferred Definition
The first thing you can do to get your components working right now is defer the component definition until the window
object is present. Unfortunately, checking if the window
exists before defining your components won't consistently work because the logic will be executed on the server and may not be run again when the code gets to the client. Most frameworks have a way to specify when code needs to be run on the "client only", so you will need to find out how to do that based on the framework you're using.
Declarative Shadow DOM (DSD)
A recent development has been the ability to define HTML templates in a shadow root declaratively. This has been dubbed declarative shadow DOM.
<my-button>
<template shadowrootmode="open">
<style>
button {
padding: 0.25rem;
border: solid 1px black;
}
</style>
<button>
<slot></slot>
</button>
</template>
My Button
</my-button>
This provides some amazing capabilities and if you defer the component definition, they can be upgraded when the client is ready. The downside is that it doesn't scale very well for something like design systems with many components. You would need to do this for every custom element on the page.
<my-button>
<template shadowrootmode="open">
<style>
button {
padding: 0.25rem;
border: solid 1px black;
}
</style>
<button>
<slot></slot>
</button>
</template>
My Button 1
</my-button>
<my-button>
<template shadowrootmode="open">
<style>
button {
padding: 0.25rem;
border: solid 1px black;
}
</style>
<button>
<slot></slot>
</button>
</template>
My Button 2
</my-button>
Teams are attempting to use this as a stop-gap for now (like @lit-labs/ssr). This often requires special code considerations like limitations on how and when you can use certain APIs, so I don't think this is an ideal solution.
WASM?
An interesting new solution from the Enhance team is that they are using WebAssembly to provide SSRing of custom elements. This is currently limited to their ecosystem, but there may be an opportunity to learn from this and try to create a more framework-agnostic approach in the future.
Future Solutions
There are some new proposals in the works that should provide a scalable solution to some of the pain points SSRing is trying to solve when it comes to web components.
Declarative Custom Elements (DCE)
Declarative Custom Elements (DCE) is a technology that may provide a more efficient approach to authoring web components. These are similar to custom elements using the Declarative Shadow DOM, but you only have to define them once and they can be used everywhere.
<template element="my-button">
<style>
button {
padding: 0.25rem;
border: solid 1px black;
}
</style>
<button>
<slot></slot>
</button>
</template>
<my-button>My Button 1</my-button>
<my-button>My Button 2</my-button>
This, in conjunction with HTML modules would provide a mechanism for custom elements that are both client-side framework SSR-friendly, reduce the dependency on JavaScript, and improve the performance of our custom elements that don't require any JavaScript. Also, like custom elements using the Declarative Shadow DOM, these custom elements can be upgraded once the client is available for more advanced user interactions.
Conclusion
If your application uses a server-side framework to render your UIs, you are safe to use web components. If you are using a client-side framework to render your UIs, there are some things you can do now to make your components work in those scenarios, but some new things are coming that should greatly improve the experience SSR for custom elements.
Posted on November 19, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.