π The Future of Server-Rendered Web Components: Enhance vs. Lit vs. WebC π


Web components have been around for a while, but server-side rendering (SSR) of custom elements has always been a tricky subject. With modern frameworks moving towards server-first architectures, the demand for HTML-first, SSR-friendly components has never been higher.
So today, let's break down three popular ways to server-render web components in Node.js using:
β Enhance
β Lit
β WebC
We'll explore how they work, their differences, and which one is best suited for building scalable, high-performance applications.
π‘ The Big Question: Can You Server-Render Web Components?
Absolutely! Web components have always been renderable on the server. Even without JavaScript, you can output basic custom elements on any backend.
Here's a simple example of an SSR-friendly custom element:
<my-custom-element>
<my-custom-header><h1>Welcome!</h1></my-custom-header>
<my-custom-button variant="primary"><button>Click Me</button></my-custom-button>
</my-custom-element>
And the CSS:
my-custom-element {
display: block;
background: lightyellow;
padding: 1rem;
}
my-custom-header {
display: block;
padding-bottom: 1rem;
border-bottom: 1px solid rebeccapurple;
}
my-custom-button[variant="primary"] button {
background: navy;
color: white;
}
π₯ No JavaScript needed. The browser renders it just fine. Butβ¦ is that enough?
In modern component-driven applications, we need:
β
Shadow DOM support
β
Declarative server rendering
β
Hydration for interactivity
This is where Enhance, Lit, and WebC come in!
β‘ Enhance: The Lightweight Server-First Framework
Enhance is a fullstack framework designed with SSR web components in mind. The best part? Itβs a standalone library, meaning you can use it in any Node.js app.
π Example Enhance Component
export default function MyElement({ html, state }) {
const { attrs } = state;
const { hello = "?", easy = [] } = attrs;
return html`
<style>
:host {
display: block;
padding: 0.5rem 1rem;
border: 3px double orange;
}
</style>
<h2>Hello ${hello}!</h2>
<p>Easy as ${easy.join(", ")}. <slot></slot></p>
`;
}
And the server-side rendering code:
import MyElement from "./my-element.js";
import enhance from "@enhance/ssr";
const html = enhance({
elements: { 'my-element': MyElement },
});
const data = { hello: "world", easy: [1, 2, 3] };
const result = html`
<my-element hello=${data.hello} easy=${data.easy}>Hello from SSR!</my-element>
`;
console.log(result);
π Why Use Enhance?
β
Simple & lightweight
β
No Shadow DOM by default (great for flexibility)
β
0kb JavaScript philosophy
Enhance is great if you want pure SSR components with easy expansion. However, it lacks built-in isomorphic rendering (components running both on server and client).
π₯ Lit: The Isomorphic Powerhouse
Lit is a popular web component library that provides both client-side and server-side rendering. Unlike Enhance, Lit uses Shadow DOM by default and works well in isomorphic setups.
π Example Lit Component
import { LitElement, html, css } from "lit";
export class MyElement extends LitElement {
static styles = css`
:host {
display: block;
padding: 0.5rem 1rem;
border: 3px double orange;
}
`;
static properties = {
hello: {},
easy: { type: Array },
};
constructor() {
super();
this.hello = "?";
this.easy = [];
}
render() {
return html`
<h2>Hello ${this.hello}!</h2>
<p>Easy as ${this.easy.join(", ")}. <slot></slot></p>
`;
}
}
customElements.define("my-element", MyElement);
π How to Server-Render Lit Components
import { render } from "@lit-labs/ssr";
import { collectResult } from "@lit-labs/ssr/lib/render-result.js";
import { html } from "lit";
import { MyElement } from "./my-element.js";
const result = await collectResult(render(html`
<my-element hello="world" easy=${[1, 2, 3]}>Hello from SSR!</my-element>
`));
console.log(result);
π Why Use Lit?
β
Shadow DOM for encapsulation
β
Great for isomorphic apps (client & server run the same code)
β
Backed by Google & has a strong ecosystem
However, Litβs SSR mechanism is still evolving, and using it requires some workarounds.
π WebC: The Future of HTML-First SSR
WebC (Web Components Compiler) is a unique, HTML-based approach to writing server-rendered web components. It was created by Eleventyβs team and is quickly gaining traction.
π Example WebC Component
<script webc:setup>
const join = (input) => input.join(", ");
</script>
<style webc:scoped="my-element">
:host {
display: block;
padding: 0.5rem 1rem;
border: 3px double orange;
}
</style>
<h2>Hello <span @text="hello"></span>!</h2>
<p>Easy as <span @text="join(easy)">...</span>. <slot></slot></p>
π₯ How to Server-Render WebC Components
import { WebC } from "@11ty/webc";
const page = new WebC();
page.defineComponents("./components/*.webc");
page.setInputPath("./page.webc");
page.setBundlerMode(true);
const { html } = await page.compile({
data: { hello: "world", easy: [1, 2, 3] },
});
console.log(html);
π Why Use WebC?
β
True HTML-first approach
β
No JavaScript required for basic components
β
Built for SSR-first workflows
WebC is perfect for Eleventy and other SSR-friendly environments, but it lacks built-in client-side hydration.
π Which One Should You Use?
Feature | Enhance | Lit | WebC |
Shadow DOM | β No | β Yes | β Optional |
SSR Support | β Yes | β Yes | β Yes |
Client Hydration | β Yes | β Yes | β Limited |
HTML-First | β Yes | β No | β Yes |
My Pick? If you love HTML-first development, WebC is the future. However, if you need isomorphic components, Lit might be a better fit. And for pure SSR setups, Enhance is a solid choice!
What do you think? Would you switch to SSR web components? Let me know in the comments! π¬π
Hope you like this breakdown! Stay tuned for more web dev insights. πβ¨
Subscribe to my newsletter
Read articles from Vivek directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Vivek
Vivek
Curious Full Stack Developer wanting to try hands on β¨οΈ new technologies and frameworks. More leaning towards React these days - Next, Blitz, Remix π¨π»βπ»