Skip to main content
slurp-dev serves a built theme on 127.0.0.1, watches it, and pushes a reload to the browser when a file changes. It can answer the theme’s API calls from a JSON file, so a page that fetches data renders with nothing else running.
That serves the current directory on http://localhost:3000.
It serves built HTML, not .slurp sources. slurp dev in a directory of templates returns 404 for every page, because it is looking for pages/index.html and there isn’t one. Build first, and serve the output, or use --source-dir below to get the rebuild loop.

Installation

slurp dev is a launcher. The server itself is a separate binary from a separate crate, and the launcher looks for it next to the slurp binary, then on PATH:
Without it the launcher exits 127 with a message saying how to build it. From a clone, cargo build -p slurp-dev-server puts slurp-dev next to slurp in target/debug or target/release, which is where the launcher looks first.

Run modes

What slurp dev supports directly. Reloads fire on any watched file changing, but nothing recompiles templates: rebuild them yourself.

Flags

These are slurp-dev’s own flags. slurp dev forwards --port and --fixtures, plus a --bind that is accepted and ignored.
number
default:"3000"
TCP port for the HTTP server and the reload WebSocket. They share one port.
path
default:"."
The directory to serve. Pages come from pages/*.html inside it, and everything else is served as a static asset.
path
Template sources. Setting it makes the server run slurp build into --theme-dir at startup and again after every .slurp change, and enables server-side rendering for dynamic [param] routes.
path
A JSON file, or a directory holding fixtures.json, mapping a request path to a canned response. See Fixtures.
url
Proxy /api/* and /auth/* here, and resolve relative {fetch} URLs against it. Combines with --fixtures: a fixture wins, and a miss falls through to the backend.
path
A JSON file seeded into every SSR render context, the same file you would pass to slurp build --globals.
string
A CSS build command to run in watch mode alongside the server, killed when the server exits. It is run through the platform shell, so PATH and node_modules/.bin resolve as they would in a terminal.
host
An extra hostname the server will answer to. Repeatable. See Security posture.
--globals seeds the SSR context only. It is not passed through to the slurp build that --source-dir runs, so a value used by a statically built page still renders empty in this mode. Pass it to your own slurp build as well if a page depends on it.

How reload works

It is a full page reload, not module-level hot swapping. There is no state preservation and nothing is patched in place. The server injects three lines before </head> (falling back to </body>, then to the end of the document) into every HTML page it serves:
That client opens a WebSocket to /_slurp/ws and reconnects every second if it drops. Three message types come back: What triggers them: the watcher looks at .slurp, .css, .js, .ts, .json, .png, .jpg, .jpeg, .svg and .webp files under --theme-dir, debounced 50 ms so one save is one rebuild. A change to a non-template file broadcasts pages: ["*"], meaning everything. A change to a .slurp file is walked through an import graph first, so editing a component reloads the pages that import it, not just the component. A parse error in a watched .slurp file pushes the overlay rather than reloading into a broken page, and fixing the file clears it.
The server logs nothing by default. Its built-in filter names a target the binary does not use, so you have to ask for logs explicitly: RUST_LOG=slurp_dev=info slurp-dev --port 3000. That prints the theme directory it resolved, the watcher starting, the fixture count and whether the backend answered.

Fixtures

A fixtures file is a flat JSON object mapping a request path to the response to return for it:
fixtures.json
Two things are answered from it: /api/* and /auth/* requests the page makes from the browser, and {fetch} URLs during SSR. A lookup tries the exact string first, then the path with any query string stripped, so /api/products?limit=10 still matches a /api/products fixture. Slurp knows nothing about what the paths mean. It replays whatever JSON the file records. A miss with no --backend-url is a 404 that names the path so you can add it:
Passing a directory instead of a file reads fixtures.json inside it. A file that does not parse, or that is not a JSON object, exits 1 at startup rather than starting up half-configured.

Dynamic routes

With --source-dir set, a request for an extensionless path that no built page matches is tried against a bracket file: pages/[slug].html catches /some-product, matching from the deepest segment first. The server then re-compiles the corresponding .slurp source with the route parameter in the context, resolves the template’s {fetch} URLs (from fixtures first, then the backend, forwarding the request’s Cookie header), and renders in development mode. If that fails it falls back to serving the pre-built bracket file as-is, which is usually a skeleton.

Security posture

It binds 127.0.0.1 unconditionally. Binding loopback does not stop an open web page from talking to it, so there are two further checks:
  • Host. A request whose Host header names anything but localhost, 127.0.0.1 or [::1] is refused with a 403 explaining why. DNS rebinding fails on this check: an attacker pointing their own domain at 127.0.0.1 makes the browser treat their page as same-origin with the dev server, and the Host header is the only place that lie is visible.
  • Origin. A present but foreign Origin is refused on every route. Without it, a cross-site simple request could drive the backend proxy with the user’s cookies attached. An absent Origin is allowed, because a normal navigation and a plain curl both send none.
The WebSocket is stricter: a missing or null Origin is refused there too, since a real browser handshake always carries one. Without that check any page on the internet could open a socket to ws://localhost:3000/_slurp/ws and read the build errors, which carry absolute local filesystem paths. If you point a hosts-file entry at the server, opt it in by name:
That admits mytheme.test and nothing else. Path traversal is refused in every encoding, including through the dynamic-route and SSR branches, and the proxy does not relay the upstream’s access-control-allow-origin header: a permissive dev backend answering * must not hand a foreign page read access to responses fetched with the user’s cookies.
This is a development server. Do not expose it. It is not hardened for network exposure, it sets cache-control: no-store on everything, it does no compression, and it proxies to whatever --backend-url it was given. Ship the output of slurp build from an ordinary static host instead.

Status

slurp-dev-server is 0.1.0 and its CLI surface is unstable. slurp dev forwards a small part of it, and the flags it does not forward may still move.

Next

CLI

Building and validating from the shell.

Editor setup

Highlighting and formatting while you write.

Pages

Routing, and how a page becomes a file.

Common mistakes

Failures that survive a clean reload.