Building a SPA (Single-page Application) is simply a analyzable puzzle: a JavaScript model that draws the view, an API serving JSON, and 2 independent codebases forced to understand each different done contracts. It is an accepted, professionalized scenario. But being a modular does not make it the only way. I want to show you different approach, 1 that is not caller but has gained traction complete the years: HTML complete WebSockets.
The thought is this: alternatively of sending JSON and assembling the HTML successful the browser, the server sends the HTML already built and the customer conscionable places it wherever it belongs. All the rendering logic stays successful the Back-End, successful a azygous language, pinch nary request for contracts aliases an API. This shape is known arsenic hypermedia aliases HTML complete the wire. What matters astir really the HTML travels is that it determines the latency and the bidirectionality of the communication. There are 3 variants:
- Over HTTP, petition by request, for illustration htmx aliases Unicorn.
- Over SSE, opening a one-way, continuous transmission from server to client, for illustration Datastar.
- Over WebSockets, a permanent, bidirectional channel, for illustration Phoenix LiveView aliases Django LiveView.
The transmission is truthful important that it determines the application's architecture and its connection pattern.
In this article I americium going to talk astir HTML complete WebSockets: the real-time and bidirectional version of the family. The 1 that lets you build a SPA pinch hardly immoderate JavaScript, successful a azygous language, pinch nary contracts and a azygous rendering engine. We will spot what it is, really it useful and erstwhile it pays disconnected compared to its HTTP aliases SSE cousins.
Origin
Chris McCord, creator of Phoenix (the astir celebrated model successful the Elixir ecosystem), presented astatine ElixirConf 2019 a exertion called LiveView. In conscionable 15 minutes he built a Twitter clone that worked successful existent time without adding immoderate rendering JavaScript aliases a celebrated model (React, Angular, Vue...) to negociate the View, proving that you could enactment successful the Back-End and beryllium productive pinch a saccharine hint of bully performance. Since past the solution has grown popular, inspiring different developers to build HTML-over-WebSockets implementations successful other languages. You tin spell backmost to the Back-End without giving up the bully parts of the Front-End.
How does it work?
Even though it mightiness not look truthful astatine first, JavaScript is utilized connected the client. Its occupation is not to render but to create a connection transmission pinch WebSockets and spot the received HTML successful the correct spot. Plus different secondary tasks for illustration animations, arena handling, etc.
McCord's solution is not to nonstop the Front-End a JSON, but HTML that needs nary preprocessing. That measurement we move the rendering load, and each its logic, to the Back-End. OK but... really do we get the server to nonstop america caller contented instantly and without making a request? Easy: pinch WebSockets.
Let's reappraisal the traditional strategy from the introduction. From the web I make an HTTP request, the browser starts the action and gets a JSON pinch each the earthy accusation successful response. The adjacent measurement is to construe it and build the corresponding HTML.
sequenceDiagram subordinate C arsenic Browser subordinate S arsenic Server C->>S: 1. HTTP petition (GET /api/article/2/) and possibly auth S->>S: 2. Query the DB S->>S: 3. Build a JSON pinch the article data S-->>C: 4. Return JSON C->>C: 5. Parse the JSON C->>C: 6. Build the HTML pinch its rendering engineWith HTML complete WebSockets, that aforesaid petition travels complete a imperishable transmission and the consequence is already assembled HTML, pinch nary JSON successful between. And since the transmission ne'er closes, the server tin moreover get ahead and nonstop changes without the customer asking.
The travel pinch WebSockets is now the following, ignoring the first relationship and authentication, which hap only erstwhile once the transmission opens:
sequenceDiagram subordinate C arsenic Browser subordinate S arsenic Server (Back-End) C->>S: 1. Sends a text: "I want /article/2/" S->>S: 2. Query the DB S->>S: 3. Render HTML pinch its template engine S-->>C: 4. Return the assembled HTML/CSS/JS"..." C->>C: 5. Place the HTML wherever it belongs
Simple, elegant and fast. The customer takes attraction of placing the HTML wherever it belongs and listening for events. The server handles the rest. You do not person to interest astir customer authorities aliases rendering logic, since everything lives successful the Back-End.
The full, analyzable cycle, including opening the relationship and authentication, would look for illustration this:
sequenceDiagram subordinate C arsenic Browser subordinate S arsenic Server (Back-End) C->>S: 1. Opens WebSocket relationship and authenticates Note complete C,S: A azygous persistent channel C->>S: 2. Sends a text: "I want /article/2/" S->>S: 3. Query the DB S->>S: 4. Render HTML pinch its template engine S-->>C: 5. Return the assembled HTML/CSS/JS"..." C->>C: 6. Place the HTML wherever it belongs Note complete S,C: The server tin besides push
changes without the customer asking (broadcast)
On apical of that, by its very architecture, it carries intrinsic advantages complete different solutions.
What are its advantages?
- There is only one rendering engine, cutting down complexity.
- You do not request to build an API: the server generates HTML and sends it to the client, pinch nary middleman.
- State lives connected the server. It is not memoryless request-response: location is simply a process per connected customer that remembers wherever it is. It is the other of htmx, which is deliberately stateless.
- Direct relationship to the database, pinch nary JSON aliases GraphQL middleman.
- Real real-time: clients person changes arsenic accelerated arsenic possible, without polling the server.
- Broadcast: the server tin push changes to each connected customer astatine once. Building a chat, a dashboard aliases a multiplayer crippled comes for free.
- Less postulation and little latency per action: a azygous persistent relationship avoids repeating the TCP handshake and the HTTP headers connected each interaction. It is not that "the WebSocket protocol is magically faster" (HTTP/2 and HTTP/3 person narrowed that spread a batch successful request-response), it is that you skip the information travel and nonstop assembled HTML.
- Build a SPA pinch hardly immoderate JavaScript, without dense frameworks for illustration React, Angular aliases Vue.
- Reasonable SEO: since the HTML is rendered connected the server, the first load is indexable. Careful though, a crawler does not spot the updates that get later complete the WebSocket, truthful the important contented must beryllium successful that first response.
- Safer against injection: since the server renders and escapes the HTML earlier sending it complete the channel, an effort to sneak successful a <script> travels arsenic inert matter and reaches your neighbor's surface arsenic plain letters, not arsenic code. The aforesaid architecture that makes a chat trivial makes it immune to XSS.
What are its drawbacks?
- The server needs more resources: it keeps a WebSocket unfastened and, usually, each client's authorities successful memory. Scaling horizontally forces you to stock that authorities (in Django, pinch Channels + an ASGI server + Redis arsenic the transmission layer). That said, the existent problem only shows up pinch a ample number of simultaneous clients, and observant creation tin mitigate it. My tract has handled peaks of 600 simultaneous readers without trouble, moving connected hardware akin to a Raspberry Pi 3 pinch different services moving alongside.
- Latency: pinch a batch of beingness latency, the "instant" consciousness suffers.
- It does not activity offline. If the relationship drops, the tract stops working. You person to creation the reconnection acquisition and responsibility tolerance.
- The first learning curve is steeper than dropping successful a <script>: moving a WebSocket server is not trivial, and you person to study to grip the LiveView pattern.
The existent landscape: which frameworks exist?
The hypermedia activity already has an implementation successful almost each language. Look astatine the transport column: the ones moving complete WebSocket (the LiveView pattern, real-time and bidirectional) coexist pinch the HTTP and SSE cousins, for erstwhile you do not request that two-way channel. You tin commencement here:
| Elixir | Phoenix LiveView | WebSocket | Yes | Mature (1.x, LiveView 1.0 successful Dec 2024) |
| Ruby | Hotwire (Turbo + Stimulus) | HTTP + WebSocket/SSE (Streams) | Yes | Turbo 8 pinch morphing |
| Python / Django | Django LiveView | WebSocket | Yes | Active (mine) |
| Python / Django | Reactor | WebSocket | Yes | Active |
| Python / Django | djust | WebSocket | Yes | New, pinch a Rust VDOM |
| Python / Django | django-unicorn | HTTP / AJAX | No | Active |
| Python / Django | Tetra | AJAX + WebSocket | Yes | Young, connected Alpine.js |
| C# / .NET | Blazor (Interactive Server) | WebSocket (SignalR) | Yes | .NET 9, pinch render modes |
| PHP / Laravel | Livewire 3 + Reverb | WebSocket | Yes | Reverb, Laravel's ain WebSocket server (2024) |
| Agnostic (JS) | htmx | HTTP + WS/SSE extensions | Yes (extension) | 2.0 |
| Agnostic (JS) | Datastar | SSE | Yes | 1.0 |
SSE, the inexpensive option
WebSockets is powerful, but keeping a bidirectional transmission unfastened per customer has a cost. And often you do not request it: if the travel is mostly server to client (notifications, a unrecorded feed, a dashboard, the tokens of an AI response), Server-Sent Events (SSE) are enough. It is the aforesaid idea, sending ready-made HTML complete the wire, but complete a plain HTTP transmission that only goes 1 way.
It is the inexpensive option: the simplest infrastructure. By not keeping a stateful process per client, it is easier to load-balance and scale.
However, it has limitations:
- It is one-way. Only the server pushes. If the customer wants to nonstop something, location is nary transmission for it: it has to make a abstracted HTTP request.
- Text only. It carries UTF-8, nary binary (WebSocket does).
- Worse for dense bidirectional work. In a chat, collaborative editing aliases a game, that loose back-and-forth complete HTTP weighs much than an always-open WebSocket.
htmx has an almost identical implementation successful tone pinch its SSE extension. You state the transmission pinch an property and the HTML that arrives successful each arena places itself:
<div hx-ext="sse" sse-connect="/updates" sse-swap="message"> Real-time contented appears here </div>Under the hood it uses the browser's ain EventSource, pinch reconnection included, and the server sends HTML fragments complete text/event-stream. The aforesaid accuracy arsenic this article, conscionable changing the transport. Along the aforesaid lines is Datastar, which unifies Alpine-style reactivity complete SSE.
The speedy rule: if you request bidirectional, low-latency connection (chat, collaboration, games), WebSocket; if you only push from the server, SSE is simpler and cheaper to operate.
Final notes
HTML complete WebSockets is not the reply to everything, and nary of these technologies is. The carrier is dictated by your problem: if you request real-time back-and-forth (a chat, a unrecorded panel, thing collaborative), WebSockets; if you only push from the server, SSE; if request-and-response is enough, htmx complete HTTP.
Every task is its ain world, pinch its ain quirks and limits. If you return distant 1 thing, fto it beryllium the thought underneath: nonstop HTML alternatively of JSON, enactment successful a azygous connection and transverse the API, the contracts and half the Front-End disconnected your list.
Trust a bully architecture, not trendy frameworks aliases patterns.
Sources
- Phoenix LiveView, charismatic docs: the canonical pattern, pinch per-client authorities connected the server and diffs sent complete WebSocket.
- Phoenix LiveView 1.0 released, Phoenix blog: the 1.0 milestone (December 2024), six years aft the first commit.
- Hotwire, charismatic site: wherever the sanction "HTML Over The Wire" comes from and why Turbo runs mostly complete HTTP.
- htmx docs: hypermedia complete HTTP, deliberately stateless, pinch WebSockets and SSE only done extensions.
- Turbo Handbook: Page Refreshes, connected Turbo 8's morphing to update only what changed and sphere scroll and focus.
- idiomorph, the DOM morphing room that respective of these solutions use.
- Datastar, the hypermedia model that bets connected SSE alternatively of WebSockets.
- Using server-sent events, MDN: really SSE useful (EventSource, automatic reconnection, Last-Event-ID, text/event-stream).
- Laravel Reverb, Laravel's first-party WebSocket server (2024): impervious that PHP besides does real-time without third-party extensions.
- ASP.NET Core Blazor render modes, Microsoft Learn: the Interactive Server mode runs complete SignalR (WebSockets).
English (US) ·
Indonesian (ID) ·