State in the Wrong Place: Shifting the Web Development Paradigm with Datastar¶
In systems engineering, an axiom is a foundational statement accepted as true without requiring proof. Based on broad experience across game development, real-time military communications, and distributed networks, one core axiom consistently stands out: putting state in the wrong place is the root cause of most real-world software failures.
While this principle applies to systems architecture as a whole, it is particularly relevant to the modern web. Although browsers are more capable and performant today than ever before, the modern web often feels heavy, bloated, and complex to build for. This report explores why this shift occurred, the architectural fallacies that sustain it, and how a lightweight, declarative hypermedia framework called Datastar offers a alternative approach by returning state to its proper authority.
This is based on: https://www.youtube.com/watch?v=W7Ki3aXgmZU
The Distributed Reality of the Web¶
The web is a distributed system, yet modern web development paradigms frequently treat it as two independent “happy paths”: one for the client, and one for the server. Single-page applications (SPAs) grew in popularity because early web servers operated on a “one request, one thread” model, which scaled poorly. Consequently, developers pushed rich interactivity, calculations, and state management to the client side.
Decades later, the industry remains within this SPA framework ecosystem. This has created a heavy dependency model; for instance, a basic “Hello World” template in some modern full-stack JavaScript frameworks can require hundreds of megabytes of dependencies before a single line of custom code is written. Developers frequently find themselves restricted to framework silos (e.g., operating as “Next.js developers” or “SvelteKit developers” rather than web developers), managing complex build pipelines, and patching an ongoing stream of ecosystem security vulnerabilities (CVEs).
This complexity is not an inherent trait of the web; rather, it is a byproduct of an ecosystem built on the wrong architectural foundations.
Architecture: Purpose-Built Engines + Declarative Data¶
A highly effective approach to building performant systems—popularized in game development—pairs a purpose-built engine with declarative data.
Consider SQLite, created by Dr. Richard Hipp. As a developer, you write a declarative statement such as CREATE INDEX. You do not instruct SQLite on how to invalidate its cache, rebalance its B-tree, or write pages to disk. You declare your intent, and a highly optimized engine handles the low-level execution.
The web browser is already a highly optimized, sandboxed, purpose-built engine designed to render hypermedia. HTML and CSS are naturally declarative; you specify your layout intent, and the browser engine determines the layout and pixel rendering.
+-----------------------+
| Web Browser |
| (Purpose-Built Engine)|
+-----------+-----------+
|
Processes Declarative
HTML, CSS, &
data-* Attributes
|
v
+-----------+-----------+
| Server-Sent Events |
| (Text Event Stream) |
+-----------+-----------+
^
|
Pushes State Updates
& Element Patches
|
+-----------+-----------+
| Backend Server |
| (Source of Authority) |
+-----------------------+
The missing link is a way to make JavaScript interactions equally declarative without introducing hundreds of megabytes of client-side machinery. This is the design space Datastar aims to fill.
Introducing Datastar¶
Datastar is an open-source, lightweight hypermedia framework designed to bring declarative reactivity to the browser using native web specifications. At its core, it focuses on two primary objectives:
1. Parsing custom data attributes easily.
2. Introducing reactive expressions (signals) to the client side.
Spec-Compliant Interactivity¶
Instead of introducing custom syntax parser layers, Datastar stays strictly within the HTML specification by utilizing custom data attributes (the data-* spec). Because the HTML parser is lenient, using spec-compliant attributes allows browser engines to utilize internal fast-paths for processing.
Using colon and underscore separators—which are valid according to the HTML specification—Datastar parses attributes into reactive instructions:
<!-- Declaring reactive client state (signals) -->
<div data-signals="{ count: 0 }">
<!-- Bind an action that mutates the state -->
<button data-on-click="$count++">Increment</button>
<!-- Declaratively bind text rendering to the signal -->
<span data-text="$count">0</span>
<!-- Declaratively modify HTML attributes based on state -->
<button data-on-click="$count = 0" data-disabled="$count === 0">Reset</button>
</div>
By constraining its scope to client-side reactivity and HTML attribute orchestration, the core Datastar bundle is highly optimized—measuring approximately 4 KB. When bundled with its default suite of plugins (including Server-Sent Events integration), the entire package remains under 12 KB, delivered as a single dependency-free script.
Deconstructing Three Web Development Fallacies¶
To build simpler, more reliable web applications, developers must unlearn several industry practices that complicate state management.
Fallacy 1: “Optimistic Updates” are Necessary¶
Optimistic updates pretend a network action has succeeded before the server confirms it, rolling back client state if an error occurs. While frequently compared to “rollback netcode” in fighting games, true rollback netcode requires a fully deterministic loop where state can be precisely simulated and replayed.
The web is fundamentally non-deterministic and asynchronous. Using optimistic updates in critical applications—such as ticket booking, medical records, or financial transactions—often forces the UI to present incorrect information to the user.
The Alternative: Instant Intent¶
Rather than lying to the user, systems can implement Instant Intent. When a user triggers an action, the UI reacts immediately in the same frame to reflect that an attempt is in progress (e.g., disabling form inputs, displaying a loading indicator), while the server retains absolute authority over the actual state update.
<form data-signals="{ processing: false }" data-on-submit="$processing = true">
<button data-disabled="$processing">
<span data-text="$processing ? 'Reserving...' : 'Book Ticket'">Book Ticket</span>
</button>
</form>
The server processes the request and sends down the authoritative state. If the seat is unavailable, the UI updates directly from the server’s response. The application avoids the complexity of client-side simulation and rollback logic.
Fallacy 2: Custom Client Code Must Be Imperative¶
Developers often struggle to isolate custom client-side behaviors (like drawing to a <canvas> or handling complex touch inputs) without entangling them in application-wide framework state.
The Alternative: Web Components¶
By wrapping highly interactive, isolated features inside standard Web Components, developers can expose declarative attributes to the outside world while keeping low-level imperative code encapsulated:
<!-- Drive a highly optimized canvas-based custom element via declarative signals -->
<custom-starfield data-bind-speed="$starSpeed"></custom-starfield>
<input type="range" data-model="starSpeed" min="1" max="10">
This separates element-level concerns (rendering pixels efficiently) from application-level concerns (state management). It also ensures the custom UI element remains reusable across any backend language or framework.
Fallacy 3: Client-Side Polling is the Best Way to Sync State¶
In client-side polling, each client runs its own timer, requesting updates from the server at set intervals. Under heavy traffic, this creates staggered, high-concurrency request spikes that can degrade backend performance. Furthermore, clients spend most of their time displaying stale data between poll cycles.
The Alternative: Server-Throttled Server-Sent Events (SSE)¶
Instead of the client requesting data, the server establishes a long-lived, read-only HTTP connection using Server-Sent Events (SSE). This allows the server to push updates dynamically.
With SSE, the server can apply backpressure and throttled updates. For example, even if thousands of internal database writes occur every second, the server can throttle outgoing SSE frame updates to once every 200 milliseconds, distributing identical state changes to all connected clients simultaneously.
The Technical Case for Server-Sent Events (SSE) over WebSockets¶
When real-time updates are required, developers often default to WebSockets. However, WebSockets bypass standard HTTP semantics, introducing several drawbacks: * Loss of HTTP/2 Multiplexing: WebSockets require an expensive protocol upgrade and run on their own TCP connection. SSE, by contrast, operates over standard HTTP and benefits from native multiplexing on HTTP/2. * Loss of Standard Headers and Compression: WebSockets do not natively support standard HTTP compression or headers out of the box. SSE stream payloads utilize standard HTTP compression (such as Brotli or Zstandard). * Connection Management: Browser engines natively manage SSE connection lifecycles, offering automatic reconnect behaviors that can be configured with exponential backoff on the client side.
An SSE event payload in Datastar is straightforward, relying on the standard text/event-stream mime type:
event: dts-patchHtml
data: <div id="ticket-status" class="sold-out">Unavailable</div>
Real-World Performance & Validation¶
Shifting state back to the server and transmitting HTML fragments over compressed SSE streams can scale effectively, even under heavy rendering requirements.
The Rendering Stress Test: Game of Life¶
To test the limits of HTML-over-the-wire, a community member built a real-time “Conway’s Game of Life” demo running on a modest blog server.
* The setup: The grid consists of 2,500 individual <div> elements.
* The workload: The server recalculates the state and streams down the entire 2,500-div grid five times per second.
* The optimization: By utilizing Zstandard window compression, the repeated HTML structure achieves compression ratios exceeding 300:1.
The demo handles high traffic smoothly because the client’s role is restricted to parsing and morphing the incoming HTML string into the DOM.
Production Migration: Instabook¶
David Nolan, a prominent software engineer known for his work in the ClojureScript and React ecosystems, migrated his company’s primary financial application (Instabook) from a traditional React architecture to Datastar.
The application—which features highly complex spreadsheet grids and interactive bookkeeping tools—saw significant improvements following the transition: * Codebase Reduction: The overall client-side footprint was reduced by over 20x in volume. * Performance Gains: Interactivity on mobile devices became faster and more responsive, primarily because the mobile CPU was freed from executing heavy client-side JavaScript reconciliation loops and virtual DOM diffing.
Scaling the Backend and Easing Testability¶
High-Performance Backends¶
Because Datastar communicates using standard, plain text-based HTTP protocols, it is entirely backend-agnostic. Backend developers can write templates in Go, Rust, Zig, Python, or even run lightweight real-time interfaces on resource-constrained embedded systems like an ESP32 microcontroller.
When paired with a compiled, highly concurrent language (such as Go or Zig), a single CPU core can easily sustain 30,000 to 40,000 concurrent, open SSE streaming connections.
Simplified Testing Stories¶
Testing complex SPAs often requires orchestrating virtual browsers, mocking API endpoints, and replicating intricate client-side state lifecycles.
With a server-driven hypermedia architecture, the frontend is primarily a projection of the server’s state. Testing end-to-end behavior often simplifies to verifying that a given server state outputs the correct declarative HTML string:
// Testing a state update is as simple as evaluating string output
func TestTicketSoldOut(t *testing.T) {
state := SystemState{TicketsAvailable: 0}
htmlOutput := RenderTicketButton(state)
if !strings.Contains(htmlOutput, "disabled") {
t.Errorf("Expected button to be disabled when tickets are sold out")
}
}
Conclusion¶
The complexity of modern web development is largely a self-imposed challenge resulting from managing synchronized state across two separate runtime environments. By shifting application state back to its rightful authority on the server and treating the browser as a declarative rendering engine, developers can eliminate large amounts of client-side code.
The transition to a hypermedia-driven architecture requires unlearning several patterns established during the SPA era. However, the reward is a simpler, more performant, and more resilient web application.
Page last modified: 2026-08-31 15:31:45