Guide · Web UI · ESP32
A real web UI, served by the chip
The web page an ESP32 serves doesn’t have to look like it’s from 2004. The same $3 chip can host a reactive interface — gauges that sweep, charts that stream, controls that respond — with no page reloads and no cloud. Here’s how the modern approach fits in flash, where the RAM actually goes, and the shortcut that skips the whole build.
Why the old string-built HTML page is a dead end
The classic ESP sketch builds a page by gluing HTML into a giant String and returning it on
every request. It works for one gauge and falls apart after that: no reactivity (every change means a full
page reload), no components, and the markup lives as unreadable string soup in your
firmware. The moment you want a second chart or a control that talks back, you’ve outgrown it.
Build a real SPA — then pack it into flash
The modern move is to build the front end like any web app — Vue, Svelte, React, or just
modern vanilla — with a bundler (Vite) that outputs a tiny, minified index.html plus
JS/CSS. You then drop those files into the ESP’s flash filesystem (LittleFS or SPIFFS) and
serve them as static assets. The chip isn’t running a framework; it’s just handing over pre-built files —
so a Svelte app and a plain one cost the ESP the same.
Live data over a WebSocket, not polling
Reactivity comes from the transport. Instead of the browser asking “any news?” on a timer, open a
WebSocket (via ESPAsyncWebServer) and let the device push
the moment a value changes:
loop(): read sensor → ws push {temp: 24.1} // send, don't be polled
browser: ws.onmessage → gauge.value = 24.1 // update in place, no reload One socket carries every widget’s data as small JSON frames; the page mutates in place. That’s what makes a served dashboard feel like an app instead of a document — and it’s exactly how the agent face reacts live, too.
Watch the RAM — serve from flash, gzip, stream
The trap is holding big things in RAM. A bundled SPA can be tens of kilobytes — don’t read it into a
String. Store the assets gzipped in flash and stream them with a
Content-Encoding: gzip header, so both the storage and the transfer are small. Keep large
constant blobs in PROGMEM, send in chunks, and free buffers early — see
how ESP32 memory works. Done right, a rich UI barely dents the heap.
Or don’t hand-roll any of it
All of the above — the bundler, the filesystem upload, the WebSocket protocol, the RAM discipline — is real work you can skip. RisalDash generates the whole front end (HTML, CSS, JS and the WebSocket wiring) from a few lines of C++: you describe widgets, bind variables, and the reactive dashboard is served for you. Its Zero-Waste UI even strips the widgets you don’t use, so you pay only for what’s on screen.
The 30-second decision
| Approach | Effort | Reactive? | Best for |
|---|---|---|---|
| HTML in a String | tiny | no (reloads) | one value, a toy |
| Hand-built SPA + LittleFS | high | yes (WebSocket) | full control, custom design |
| RisalDash | a few lines | yes (built in) | a real dashboard, fast |
Rule of thumb: a single readout → a string page is fine; a bespoke product UI → build the SPA and own it; a real dashboard without the plumbing → let it be generated.
See a reactive dashboard — gauges, charts, controls, a live map — served from a chip.