Repository navigation
v0.2.1
Two commits since v0.1.23: the crate was split into feature-gated kits, and the base layout is now embedded with every frontend asset derived from a single source.
Feature gates
- Split into independently gated kits —
webserver,pages,templates,assets,analytics,sitemap,observability,telegram,preset— withdefault = []. A consumer compiles only what it enables: 19 transitive crates by default, down from 156 unconditional. - A sidecar still boots with no frontend: no templates, no icons, no analytics, no
static/.
The embedded base layout
basenow ships inside the crate. No project owns a copy, and a project file that would shadow it is a boot error naming the offender. The<head>is pure function — spec conformance, Open Graph, JSON-LD, icons, resource hints — so it is solved once and inherited rather than copy-pasted and left to drift.- A page supplies one
bodyblock and extends it. The layout contributes no chrome:<body>is bare.
frontend — the line between a site and a service
WebServer::frontend(FrontendParams)turns on everything a website needs. Each required input is a field, so forgetting one is a compile error, not a page that silently ships without a share card.- Boot-time asset validation. Every declared stylesheet, script, social image and generated icon is proved to resolve before the server binds, and every miss is reported at once. There is deliberately no fallback to the un-hashed path — that turns a misconfiguration into a link that 404s for the visitor and reports nothing back.
robots.txt,site.webmanifestand the sitemaps are generated andhumans.txtis embedded; all are served from memory, so a production image stays runnable read-only.- Adds
SiteEntity,ThemeColor, JSON-LD support, and a pre-paint theme script. - Every frontend gets the 404 page data for free; 404s are served at the requested URL with a real 404 status instead of redirecting to
/404(a soft 404 returning 200).
Icons
- Exactly one source —
favicon.svg, or a 512×512favicon-512.pngfor art that cannot be vectorised — andfavicon.ico,apple-touch-icon.png,icon-192.png,icon-512.pngderive from it. Every derived size is a downscale from 512, so a smaller source is refused rather than upscaled. - The icon directory is a closed set; any other file is a boot error. A stale icon from a previous design is otherwise invisible until the wrong picture turns up in a browser tab.
Static assets
- Hashing is a build step, never a startup step. Startup hashing was not idempotent, and an in-place restart 404'd every asset on the site.
gen-static-assetsandgen-static-scriptsare subcommands of your own server binary — no shim binary, no per-project glue.cache-buster.jsonandstatic/script/generated/are build outputs. Consuming projects must gitignore both.
Analytics and error monitoring
- First-party byte relays for Plausible and Sentry, on routes derived from the project name. Both vendors' origins sit on blocklists — Plausible puts the cost at 5–25% of visitors — and a per-project path means no single filter rule can take out every site at once.
- Bytes are relayed, never re-reported: a server that interpreted a browser error and re-raised it through its own SDK would file it as a server problem with the wrong stack. The tunnel's destination comes from the configured DSN, not the envelope, so it cannot be used as an open relay.
- The true client IP is forwarded as
X-Forwarded-For; without it Plausible's bot filter rejects proxied events outright. - Replaces the old frontend error logger and the scitylana script.
Observability
bootstrap!initialises Sentry on the main thread before building the runtime, so the hub reaches worker threads; the guard drops after the drain. It stays a macro so the release names your application.- Sentry's
environmentis now set — local and production errors previously shared one bucket.
Fixes
- Graceful shutdown no longer sleeps out the drain window. The window was passed into
with_graceful_shutdown, which postponed the drain by ten seconds instead of bounding it: the server kept accepting connections the whole time, then drained instantly. It is now atimeoutaround serving whose clock starts when the drain does. An idle server stops in ~0.02s instead of ~10s. - The generated
CACHE_BUSTERexport carries real JSDoc, sodeno doc --lintpasses.
Breaking changes
BaseSettingsis replaced by per-kit configs, allWSB_-prefixed.Environmentis nowLocal | Productionand required.TemplateDatais replaced byBaseTemplateData+PageTemplateData, merged into a borrowed per-renderTemplateData. The copyright year is computed per render rather than cached at boot.html/layouts/base.hbsmust be deleted from consuming projects — it is now a boot error.- Consuming projects must supply one icon source and gitignore the two build outputs.
Dependencies
axum 0.8, axum-extra 0.12, sentry 0.49, reqwest 0.13, tower-http 0.7, sitemap-rs 0.4, md5 0.8. Docker stages on rust 1.98.0, deno 2.9.5, node 26.7.0, alpine 3.24.1, sass 1.103.1. Drops the dead futures / serde_yaml / pulldown-cmark / rand dependencies.
Rust webserver-base 0.2.1 · JSR @todd/webserver-base 0.1.1
Full Changelog: v0.1.23...v0.2.1