Dev Server

ReeWeb's development server serves templates and markdown files directly - no SSG step required. It renders .ree and .md files on demand, provides live reload via WebSocket when you edit sources, and serves static assets from both src/public/ and the project root static/ directory.

The dev server is started via the bun dev command, which runs scripts/dev/orchestrate.ts: it performs the one-shot setup steps (dynamic asset sync, image preparation, initial CSS build), then spawns the Tailwind CSS watcher (--watch=always) and the dev server child process (scripts/dev.ts) side by side.

Starting the Dev Server

bun dev

This single command runs the Tailwind watcher and the dev server side-by-side. Open http://localhost:3000 in a browser.

The port is read from PORT in .env (the example .env ships PORT=3000); pass --port to override it. The default source directory is ./src/public.

CLI Options

The dev server accepts these flags when invoked directly:

bun scripts/dev.ts [--port 3000] [--public ./src/public]
FlagAliasDefault / SourceDescription
--port <n>-pPORT (env)Port the server listens on
--public <dir>--dir./src/publicSource directory with templates and assets
--help-h-Print usage and exit

All flags are optional. Running bun dev passes no flags and uses the defaults.

Live Reload

The dev server injects a WebSocket client script into every rendered HTML page. When a source file changes, the server broadcasts a reload message to all connected WebSocket clients, and the browser refreshes automatically.

How It Works

  1. The dev server runs a WebSocket endpoint at /__reload
  2. Every rendered page receives an injected <script> that connects to this WebSocket
  3. The file watcher monitors src/public/ for changes to .ree, .md, .json, and .ts files
  4. Changes are debounced at 100ms - rapid edits don't trigger multiple reloads
  5. When a .json file changes, translations are hot-reloaded without needing a full restart - the route map and locale names are rebuilt in-memory

What Triggers a Reload

File ChangeBehavior
.json (translation)Hot-reloads translations + route map, then triggers browser refresh
.ree (template)Triggers browser refresh (template re-read from disk on next request)
.md (markdown)Triggers browser refresh
.ts (data loader)Triggers browser refresh

What Triggers a Full Restart (via dev watcher)

File ChangeBehavior
scripts/dev.tsFull restart of both the Tailwind watcher and dev server
.envFull restart (environment variables are read at startup)
bunfig.tomlFull restart

Locale Resolution

The dev server resolves the locale from the URL path following the same rules as the SSG script:

/                   Slovenian (default) at root
/about/             Slovenian /about/
/en-us/             English homepage
/en-us/about/       English /about/

The resolution algorithm:

  1. Check if the first URL segment matches a configured locale in config/supported_locales.ts (e.g. en-us) - the segment is compared case-insensitively, so /EN-US/ also resolves
  2. If it does, set the active locale to the canonical BCP 47 code and strip the prefix
  3. If not, use the default_locale (configured in supported_locales.ts)

The resolved locale + canonical path is then looked up in the route map (which includes localized route_name substitutions) to find the correct template or markdown file.

Built-in Data Variables

Every template rendered by the dev server receives these variables automatically in its data context. You access them as props.xxx in templates:

VariableSourceDescription
props.localeURL resolutionActive BCP 47 locale ("en-us", "sl-si")
props.langURL resolutionSame value as props.locale (kept for the shared template engine's variant lookup)
props.html_langDerivedShort language subtag ("sl") for the <html lang="..."> attribute
props.locale_url_prefixDerived"" for default locale, "/sl-si" for others
props.active_localesConfigList of locales shown in the locale picker
props.locale_namesConfigMap of code → display name
props.locale_self_namesDerivedMap of code → native name from each locale's own translation file
props.default_localeConfigDefault locale code
props.base_urlHard-codedAlways "/"
props.site_urlHard-codedAlways "" (set during production SSG)
props.hreflang_linksHard-codedAlways [] (set during production SSG)
props.site_nameHard-coded"Dev"
props.yearRuntimeCurrent year for copyright
props.is_devHard-codedtrue
props.rendered_atRuntimeISO string of when the render happened
props.request_urlResolvedFull URL of the current page, including the locale prefix
props.canonical_pathResolvedCanonical path (without locale prefix)
props.locale_urlsDerivedMap of locale → URL prefix ("" for default, "/{locale}" for others)
props.helperscreate_template_helpers()Object of template helper functions

Template Resolution

The dev server resolves templates in this order:

  1. Hash map lookup - checks the pre-built canonical → template map
  2. Reverse route map - if the path is a localized route, resolves back to canonical
  3. Direct file check - tries {path}.ree, {path}.md
  4. Index file check - tries {path}/index.ree, {path}/index.md
  5. 404 - if nothing matches, returns a 404 Not Found response

Locale-Variant Templates

Templates can have locale-specific variants. The resolution chain:

{name}.{requested_locale}.ree → {name}.{default_locale}.ree → {name}.ree

For example, with locale=de-de and default_locale=sl-si:

  1. Try about/index.de-de.ree - not found
  2. Try about/index.sl-si.ree - found (used)
  3. Try about/index.ree - fallback

This applies to every template load - pages, layouts, includes, and components.

Static File Serving

The dev server serves static files from two locations:

  1. src/public/ - templates and their sibling assets (CSS, images)
  2. static/ - project-level static assets (compiled CSS, JS, favicon)

Static files are served with Cache-Control: no-cache headers to ensure changes appear immediately during development. The server checks src/public/ first - if a file exists there and isn't a .ree, .md, .json, or .ts file, it's served directly. If not found, it falls back to static/.

Generated SSG Artifacts (sitemap & feeds)

sitemap.xml and the RSS/JSON feeds (feed.xml, feed.json) are SSG artifacts - they're emitted to dist/ by bun sitemap / bun rss, not served from src/public/. To avoid 404s on links like /sitemap.xml during development, the dev server serves the last-built copy straight from dist/ as a convenience:

Request pathServed from
/sitemap.xmldist/sitemap.xml
*/feed.xml, */feed.jsonthe matching file under dist/

These copies are stale until you regenerate them - run bun ssg (or bun sitemap / bun rss) to refresh dist/. If the artifact hasn't been generated yet, the dev server returns a 404 with a hint telling you which command produces it.

robots.txt is intentionally not served from dist/: in dev it's served from the source src/public/robots.txt (which contains Disallow: /) so the dev server stays unindexable.

Error Handling

  • 500 errors - Template compilation errors (syntax errors, missing includes, unclosed blocks) return a 500 Error page with the error message details
  • 404 errors - Unknown paths return a simple 404 Not Found page
  • Missing source directory - If --public points to a non-existent directory, the server exits with an error immediately