Skip to content

Repository files navigation

html-over-websockets

License: MIT Node Client JS

A real-time support-ticket dashboard with HTML over WebSockets — no framework, no build step, 1,772 bytes of client-side JavaScript in the entire application.

The server renders HTML fragments and pushes them over a WebSocket. The client swaps fragments into place and sends back tiny JSON intents. That's the whole architecture.

Demo — real-time ticket dashboard rendered server-side, updated over WebSockets

Open it in two windows side by side: change something in one, watch the other update instantly.

Run it

Requires Node ≥ 18.

npm install
npm start        # http://localhost:3000

Why?

Modern front-end defaults to "real-time = SPA + state library + virtual DOM + bundler". But if the server already knows what changed, why ship a rendering engine to the browser to figure it out again? This project is the counter-example: one source of truth on the server, ready-made HTML mailed to every connected client.

Metric This app Typical React equivalent
Client JS shipped 1.7 KB 45–150 KB min+gzip
Dependencies 1 (ws) hundreds
Build step none bundler + config
Hydration not needed grows with tree size
Multi-user sync free (one truth, broadcast) manual

How it works

Two channels, opposite directions:

┌──────────────┐   user intents (JSON)    ┌──────────────────┐
│   Browser    │ ───────────────────────▶ │      Server      │
│              │                          │                  │
│  swaps HTML  │ ◀─────────────────────── │  mutates state,  │
│  fragments   │   HTML fragments         │  renders HTML    │
└──────────────┘                          └──────────────────┘

Up (client → server): tiny JSON intents, e.g. {action: "change-status", id: 2, status: "done"}.

Down (server → client): rendered HTML fragments, applied by two declarative rules:

  1. An element carrying an id replaces its twin in the DOM (out-of-band swap).
  2. An empty <tr data-delete-id="N"> removes the row with data-row-id="N".

Interactive elements declare intent via data-action attributes; three global event listeners (submit, change, click) route them over the socket. Because listeners hang off document, freshly swapped fragments are interactive immediately.

Files

File Purpose
server.js HTTP shell page + WebSocket server + render functions (~180 lines)
client.js The complete client-side JavaScript (~50 lines)
style.css Styling

Companion article: "I Built a Real-Time App With 1.7 KB of JavaScript — No Framework, No Build Step" (draft in this repo's history · published on Medium).

Trade-offs (honest version)

Great for: dashboards, admin panels, monitoring screens, chat, live tables — anything where the server holds the truth and multi-client sync matters.

Not for: offline-first/PWA apps, canvas or drag-and-drop editors, very high-frequency updates, optimistic UI. Concurrent edits are last-write-wins.

One subtle cost: swapping a large region re-creates its DOM, so focus/selection inside it resets. Keep swap regions small.

See also

The same philosophy, industrialized:

License

MIT

About

Real-time app with HTML over WebSockets — no framework, no build step, 1.7 KB of client JavaScript

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages