Approach

Control and responsibility back to the server

We build web applications so that the server regains control over the application state. This page describes the principles behind that and answers the objections we hear most often.

Our architectural principles

We choose technologies based on impact, not trends. These principles permeate all our projects, regardless of frameworks or hype.

One long-running server process

A web application doesn’t have to build itself up for every request and throw everything away afterwards, as PHP’s classic request/response model does. We run it as one server process that keeps running: in Go, or in PHP with an asynchronous runtime such as Swoole.

Such a process enables the efficient implementation of:

  • long-lived connections
  • parallel I/O operations
  • event loops

while simultaneously accessing a large, stable ecosystem.

The result is high performance without sacrificing maintainability, debuggability, or tooling. On top of that, the code is strictly typed: static analysis verifies it early on, and it stays stable in the long term. In Go the compiler does this, in PHP a tool such as PHPStan (including generics).

Pushing instead of Polling (SSE)

Modern web applications don’t need to constantly “check” for changes.

With Server-Sent Events (SSE), the server maintains a persistent HTTP connection and actively pushes state changes to the client.

SSE is:

  • simple, standardized HTTP
  • highly compressible (e.g., with Brotli or zstd)
  • ideal for high-frequency payloads

In practice, this allows for very high compression rates, especially with continuous updates. This is achieved without complex protocols or client state management.

Backpressure & Controlled Data Flow

An often underestimated advantage of server-authoritative architectures is backpressure:
The server determines when, how often, and how much data is sent.

A typical pattern:

  • The server calculates the new state once per change or at a fixed frequency (e.g., 5 Hz).
  • All connected clients receive the same update stream (e.g., via SSE).

Regardless of whether 1 or 1,000 users are connected, the following remains constant:

  • The number of state calculations is constant.
  • The data flow is controllable.

What each connection costs is its compression. Brotli keeps its window per connection, so each stream compresses every frame for itself and holds its own memory for it. The compression level and the window size are the knobs that trade memory and CPU for bytes on the wire: the higher the level and the larger the window, the more memory each connection needs and the fewer bytes it sends.

Measured on our Kanban board: the server renders the board once per change for everyone, in 2.4 ms per frame at 200 tabs, and a frame after one move costs 0.5 to 1 kB on the wire. A stream that falls behind gets the newest frame instead of a backlog. At 200 busy tabs, compression was most of 5.1 CPU cores. Each busy stream holds about 2.2 MB of brotli state at quality 4, and Go’s garbage collector roughly doubles that in memory (RSS). Quality 3 needs about a quarter less memory and sends about 60% more bytes.

The result is stable load profiles and highly predictable system behavior: the state calculation is not multiplied, and what each connection costs can be measured and tuned.

Embedded Databases (e.g., SQLite)

Network databases are often limited by latency in many workloads: Every query is at least a round trip, regardless of the server's processing speed.

Embedded databases like SQLite run in the same process (or at least on the same machine) as the application logic. This eliminates network round trips and enables extremely fast reads, especially in typical query-heavy applications.

This is particularly effective when combined with CQRS:

  • Writes are directed to a controlled path (e.g., a central write store).
  • Reads are handled from optimized, local read models (e.g., SQLite, Projections).

How much performance this gains depends on the data model and access patterns: each query no longer pays the latency of a network round trip.

view = f(state)

The display is purely a function of the state.

There is no implicit or distributed view state on the client.

This means:

  • the same state always produces the same display
  • rendering is deterministic
  • the view is always fully reconstructible

The server maintains the authoritative state, clients receive updates, and the UI follows automatically. Entire classes of errors (inconsistent views, lost updates, and hard-to-reproduce edge cases) are completely eliminated.

Reactive UIs are also possible in a server-centric architecture: In the client, we use signals to map state changes in a targeted and efficient way in the interface, without SPA-typical overhead.

Because the server sends finished HTML rather than data, the browser only receives what the page shows. A JSON API often returns more fields than the interface needs, and anyone who opens the response in the browser sees all of them: a common cause of data leaks (information disclosure).

DOM‑Morphing

Instead of re-rendering entire views, only the necessary changes to the DOM are made.

DOM morphing involves the client comparing the current DOM with server-generated HTML and applying targeted updates.

Advantages:

  • Minimal DOM operations
  • Preservation of focus, scroll, and input states
  • No client-side rendering pipeline, no virtual DOM

The server remains the source of truth, and the client remains lean.

Multiplayer & Collaborative Systems

Real-time collaborative software becomes significantly simpler when the server has sole authority over the state.

The model is clear:

  • Clients send intents (actions)
  • the server decides on the new state
  • all clients receive the same, consistent update stream

This eliminates:

  • competing client states
  • complex conflict resolution
  • difficult-to-test synchronization logic

The system behaves deterministically, reproducibly, and remains manageable even with an increasing number of users.

CQRS: Command-Query Responsibility Segregation

Read and write operations have different requirements. CQRS deliberately separates these:

  • Commands (Writes): classic HTTP requests that trigger actions
  • Queries (Reads): continuous stream (e.g., SSE) that delivers state changes

This separation enables:

  • targeted optimization of reads and writes
  • clear, traceable data flows
  • high scalability without additional architectural complexity

Event Sourcing

Instead of directly persisting the current state, business events are stored as the primary source.

The pattern:

  • Actions generate events (e.g., UserRegistered, OrderConfirmed)
  • The current state is derived from the event history
  • An event bus (e.g., NATS) distributes these events internally (e.g., to projections, SSE streams, or secondary processes)

In combination with server-side authority, this offers clear advantages:

  • Complete traceability and auditability
  • Deterministic replays (state can be reconstructed at any time)
  • Natural integration with CQRS (events → read models)

For clients, this means: They subscribe to state changes, not implementation details. Real-time updates, replays, and new views can be added without invasive modifications.

Our case studies show these principles in practice, with the source code and the raw data.

Objections from practice

We frequently hear these questions and concerns in technical reviews and architectural discussions. They arise from real-world project experience, as do our answers.

That sounds technically sound, but what are the business benefits?

The primary benefit lies not in the technology itself, but in the economic impact:

  • Lower development costs due to reduced architectural overhead and shorter feedback cycles
  • Predictable performance thanks to backpressure and server-side control
  • Reduced operating costs, as no complex cloud or microservice infrastructure is required
  • Faster changes because logic and state are centralized

In short: You invest less in infrastructure and complexity, and more in tangible business value.

This approach is particularly well-suited for organizations that prioritize stability, predictability, and long-term maintainability.

But SPAs are the industry standard

Single Page Applications are widespread, but widespread adoption doesn’t equate to suitability.

The SPA approach became established primarily for historical reasons:

  • Limited browser APIs in the past
  • The desire for app-like UX during the era of slow servers
  • Strong tooling and framework ecosystems

Today, the landscape has fundamentally changed:

  • Servers are powerful and always connected
  • Modern browsers natively support push, streaming, and transitions
  • Latency and complexity are often the dominant cost factors

Therefore, “industry standard” describes a state of habit rather than a technical necessity.

We don’t choose architectures based on market share, but rather on:

  • functional suitability
  • complexity profile
  • long-term costs and risks

In many real-world projects, a server-authoritative approach leads to simpler, more stable, and more economical systems, even if it's less heavily promoted.

Without a SPA, the application feels slow

The perceived “app feel” isn't primarily created by a client framework, but by:

  • low latency
  • targeted updates instead of full reloads
  • clean transitions

Server-side rendering, SSE, signals, and view transitions enable highly responsive UIs, without complex client state and with much less resource consumption.

That’s not “modern” at all

The concepts described are based on current web standards and modern runtime architecture.

“Modern” here doesn't mean maximum abstraction, but rather:

  • clear responsibilities
  • manageable complexity
  • and long-term maintainability

And that's precisely what this approach is designed for.

What about Offline-First?

Offline-First is a legitimate, specialized requirements profile, but not a sensible default for most web applications.

In many business applications:

  • Users work predominantly online
  • Consistency is more important than local autonomy
  • Conflict resolution and merge logic are technically complex and expensive

Offline-First typically requires optimistic updates, meaning UI states that assume a successful action before the server confirms it. This means the client temporarily maintains its own version of the truth, with significant consequences:

  • Rollbacks for rejected actions (validation, permissions, business rules)
  • Conflicts with parallel actions, multiple tabs, or collaborative users
  • Hard-to-reproduce edge cases and significantly higher test complexity

This complexity shifts entirely to the client and scales poorly with increasing business logic.

From our perspective, true Offline-First is primarily a case for native applications.
Native applications provide operating system APIs, persistent storage models, and clearly defined lifecycles to manage offline states cleanly and reliably.

Therefore, for web applications, we prioritize online-first with robust degradation:

  • The server remains authoritative.
  • Brief connection interruptions are mitigated.
  • Actions are explicitly confirmed or rejected.

If true offline capability is essential for a business purpose, we recommend a dedicated native solution instead of designing the entire web architecture around a single exception.

Scaling only works with microservices

Scaling is primarily a load and data flow problem, not an architectural label.

If:

  • state calculation is decoupled
  • reads and writes are separated (CQRS)
  • the server exerts backpressure

a modular monolith can scale very effectively, more efficiently and cost-effectively than distributed systems.

SQLite is not to be taken seriously / not suitable for production systems

SQLite is often perceived as an “embedded toy database.” However, this assessment is too simplistic.

Facts:

  • SQLite is transactional, ACID-compliant, and extremely mature.
  • It has been used for years in security- and business-critical systems (browsers, operating systems, mobile platforms).
  • Performance is not limited by network latency, but primarily by CPU and I/O.

The deployment pattern is crucial:

  • SQLite is ideally suited for read-heavy data models, and with a single writer for many writes too.
  • Writes are deliberately controlled (e.g., via CQRS, event sourcing, or central write stores).
  • Concurrency is handled architecturally, not left to the database.

We measured what a single writer handles on our Kanban board: it gathers the waiting commands into one transaction per batch, each command in its own savepoint. That way it handles about 13,800 moves a second with 200 people moving cards at once, with synchronous=FULL, as durable as PostgreSQL’s defaults.

In this context, SQLite is not a compromise, but a strategic tool:

  • extremely low latency
  • minimal operational complexity
  • very high predictability

In short: SQLite is not a replacement for every database, but in the right architectures, it is often the most efficient choice.

But there’s already React Server Components?

React Server Components pursue a similar goal to our approach: less client state, more responsibility on the server. The basic idea is sound and confirms a trend we've observed for years, namely the relocation of logic, data access, and rendering to where they are more controllable.

The difference, however, lies in the implementation. React Server Components address this issue within a specific framework and a complex build and runtime model. Rendering, data flow, and reactivity remain tied to React, its tooling, and its abstractions. This works well within this ecosystem but inevitably introduces dependencies and conceptual coupling.

Our approach is deliberately framework-agnostic. Instead of component-based server rendering, we rely on open web standards: HTTP, streaming, events, and clearly defined state models. The server is authoritative for state and business logic, changes are streamed as events, and the presentation deterministically follows the current state. Frontend responsiveness is achieved not through re-executing components, but through targeted updates based on state changes.

Especially for real-time, multiplayer, or continuously updated applications, this state-driven push model is more natural than a request-driven render model. It reduces implicit state, avoids hydration and re-render complexity, and remains simpler to manage operationally.

In short: React Server Components demonstrate that we're on the right track. However, we're consistently pursuing this path further, with fewer dependencies, clearer data flows, and an architectural model that remains viable in the long term, independent of any single framework.

Modern frontends require a large npm ecosystem

While a comprehensive client ecosystem sounds productive, in practice it often brings significant side effects:

  • Dependency hell due to transitive dependencies and incompatible major releases
  • node_modules as a black hole: large installations, long build times, and dependencies that are difficult to understand
  • Security risks due to a high number of external packages with varying maintenance quality
  • Forced upgrades that are not driven by business needs but by tool end-of-life

A server-centric approach drastically reduces dependence on volatile client libraries. Fewer dependencies mean:

  • a smaller attack surface
  • more predictable maintenance
  • a longer software lifespan

The focus shifts from tool maintenance to stable, value-adding functionality.

If your objection isn’t here, write to us.