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.
Open it in two windows side by side: change something in one, watch the other update instantly.
Requires Node ≥ 18.
npm install
npm start # http://localhost:3000Modern 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 |
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:
- An element carrying an
idreplaces its twin in the DOM (out-of-band swap). - An empty
<tr data-delete-id="N">removes the row withdata-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.
| 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).
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.
The same philosophy, industrialized:
- htmx — hypermedia-driven apps over plain HTTP
- Phoenix LiveView — Elixir's battle-tested take
- Hotwire / Turbo — Rails' version
- Datastar — hypermedia + signals
MIT
