How It Works #
WebUI is a language-agnostic server-side rendering framework that splits template processing into three distinct phases: build, server render, and client hydration. Each phase is optimized for its role, eliminating redundant work and keeping runtime overhead to a minimum.
Build Time Server Render Client Hydration
โโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโ
HTML + CSS + TS โ Protocol + JSON โ Web Components
webui build state โ HTML hydrate as islands
Build Phase #
The webui build CLI command transforms your template source files into an optimized binary format. This is a one-time cost - the output is reused for every request.
During the build, WebUI:
- Parses HTML templates - scans component directories for
.html,.css, and.tsfiles - Discovers web components - identifies custom elements by their hyphenated tag names
- Compiles expressions - resolves
{{bindings}},<if>conditions,<for>loops, and attribute directives into indexed slots - Separates static from dynamic content - static HTML becomes pre-serialized byte fragments; dynamic content becomes keyed slots that map to state values
- Emits output artifacts:
protocol.bin- binary Protocol Buffer containing the compiled template structure- CSS files - scoped styles for each component
- JS bundles - client-side Web Component classes for hydration
webui build ./src --out ./dist --plugin=webui
The binary protocol is the key to WebUI's performance. By moving parsing, expression compilation, and template analysis to build time, the server never repeats this work.
Server Render Phase #
At runtime, the server handler loads the compiled protocol once at startup and reuses it for every request.
For each incoming request, the handler:
- Receives a JSON state object containing the data for that page
- Walks the compiled protocol fragments in order
- Copies static fragments directly to the output buffer (no processing needed)
- Resolves dynamic fragments by looking up keys in the JSON state
- Emits the final HTML response
โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ
โ protocol.bin โ โโโ โ Handler โ โโโ โ HTML output โ
โโโโโโโโโโโโโโโโ โ + JSON state โ โโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโ
What the handler does NOT do #
- No template parsing - already done at build time
- No AST walking - the protocol is a flat list of fragments
- No expression compilation - expressions are pre-compiled to key lookups
- No JavaScript runtime - the server is pure native code (Rust, C, or any FFI host)
Declarative Shadow DOM #
The rendered HTML includes Declarative Shadow DOM markup. This means the browser can display fully styled component content before any JavaScript loads:
<my-card>
<template shadowrootmode="open">
<style>/* scoped styles */</style>
<h2>Card Title</h2>
<p>Content rendered on the server</p>
</template>
</my-card>
The user sees rendered content immediately - no blank page, no loading spinner.
Client Hydration Phase #
After the browser renders the server HTML, JavaScript loads and Web Components hydrate as independent islands of interactivity.
How hydration works #
- Custom elements upgrade - the browser calls
connectedCallbackfor each registered Web Component - Shadow root detection - the framework finds the existing Declarative Shadow DOM root (it does not recreate the DOM)
- Bindings wired - template expressions (
{{count}},?disabled) are connected to class properties - Events connected -
@click,@keydown, and other event handlers are attached - Reactive state activated -
@observableproperties become live; changes trigger targeted DOM updates
Islands Architecture #
Each Web Component is a self-contained island. Components hydrate independently - a <search-bar> can be interactive while a <footer-links> component stays as static server-rendered HTML.
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Page โ
โ โ
โ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโ โ
โ โ search-bar โ โ product-grid โ โ
โ โ (hydrated) โ โ (hydrated) โ โ
โ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ footer-links โ โ
โ โ (static - no JS) โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Only components that need interactivity ship JavaScript. Static content stays as plain HTML with zero JS cost.
The SSR Mental Model #
Understanding the relationship between server and client is critical for building correct WebUI applications.
The server is the source of truth for the initial render #
Every value bound in a template - {{expression}}, <for each="item in items">, <if condition="expr"> - must have a corresponding key in the server state JSON. The handler resolves bindings by looking up keys in this JSON object. If a key is missing, the binding renders empty or the condition evaluates to false.
Derived state belongs in the server or the template #
If a value must appear in the initial HTML, it must come from the server state JSON. Use template expressions for simple derivations:
<!-- Template expressions work on both server and client -->
<span>{{firstName}} {{lastName}}</span>
<if condition="items.length">{{items.length}} items</if>
For complex derived values, compute them on the server and include them in the state JSON.
The client handles interactivity after hydration #
Once components hydrate, user interactions are handled entirely on the client. Sorting a list, filtering results, toggling a panel - these operations mutate @observable properties directly, and the framework updates the DOM reactively.
Server: "Here's the initial HTML with all the data."
Client: "Got it. I'll hydrate the interactive parts and take over from here."
What Makes This Fast #
WebUI's architecture is designed so that the most common operation - rendering a page - does the least possible work.
| Technique | Impact |
|---|---|
| Pre-serialized static fragments | Copied byte-for-byte to the output buffer - no processing |
| Key-based dynamic resolution | Simple hash map lookup against JSON state - no expression parsing |
| No runtime allocations for structure | Template shape is fixed at build time; only data values vary |
| No JavaScript on the server | Native code (Rust/C) handles rendering - no VM startup, no GC pauses |
| Declarative Shadow DOM | Browser renders content before JS loads - no white flash |
| Islands Architecture | Only interactive components ship JS - static content has zero client cost |
| Binary Protocol Buffer | Compact, zero-copy deserialization - faster than JSON or text templates |
The result: server render times measured in microseconds, not milliseconds. First Contentful Paint that doesn't depend on JavaScript. And client-side interactivity that activates without rebuilding the DOM.