Installation
ReeWeb has one hard prerequisite: Bun. Bun is the runtime, package manager,
bundler, and test runner. bun create fetches the public starter repository and
the install task fetches the other tooling it needs (Git is only required if
you prefer to clone the starter yourself).
Installing Bun
curl -fsSL https://bun.sh/install | bash
powershell -c "irm bun.sh/install.ps1|iex"
Verify the install:
bun --version
The output should be a version number. If command fails, the installer's directory isn't on your PATH.
Starting a Project
The recommended way to start is bun create, which fetches the starter as a
tarball (so the template's Git history never arrives) and initializes a fresh
repository with a single initial commit. Cloning the repository works the same
way - the installer re-initializes Git for your site either way.
bun create reepolee/reeweb __PROJECT_DIR__
cd __PROJECT_DIR__
bun dev
Prefer git clone if you want the repository cloned rather than fetched as a
tarball:
git clone https://github.com/reepolee/reeweb.git __PROJECT_DIR__
cd __PROJECT_DIR__
bun install
bun dev
bun reeweb:install runs automatically: it is a postinstall step, so bun create
fires it during the fetch and your first bun install on a cloned checkout fires
it too - there is no separate command to run. It fetches ReeWeb's
tooling (the reettier and reesql formatters, libvips, and the vendor/
browser assets), creates .env when needed, installs Bun dependencies, then
replaces the starter Git repository with one for your project and makes its
initial commit. It does not build CSS or prepare images; bun dev and bun ssg
do that when they run.
The install task pulls Tailwind CSS v4, tailwind-merge, TypeScript, Bun's
type definitions (@types/bun), and @cloudflare/workers-types as dev
dependencies. None of them ship to the browser: ReeWeb has zero runtime
dependencies. When the public starter has changes you want, use the Upgrade
Guide to compare it with your site and
choose the files to adopt.
Next, open .env and set SITE_URL to your real domain. The installer copies
.env.example, which ships SITE_URL=https://example.com, and that value is
baked into sitemap.xml, robots.txt, the feeds, and every canonical and
hreflang link the first time you build.
Generating the Static Site
bun ssg
bun ssg is the whole production build, not just the HTML render. It runs, in
order:
dynamic:sync, which pulls dynamic assets whenREEPOLEE_API_URLis setprepare:images, which generates responsive images fromassets/images/css:build, which compiles and minifies Tailwind CSSscripts/ssg.ts, the static site generator itselfog:images, the Open Graph card for every page (see libvips)sitemap, thenrss, thenssg:search
The generator step (scripts/ssg.ts) is the one that:
- Renders all
.reetemplates and.mdfiles to static HTML - Copies static assets (CSS, JS, images, fonts) to the output directory
- Generates localized routes for every configured locale
- Emits redirects for paths declared in
config/redirects.ts
Because bun ssg already covers the sitemap, feeds, and search index, you do
not need to run those separately after it. The individual scripts in Other
Commands exist for re-running a single stage.
The output goes to dist/. Serve it with any static file server:
bun preview
This starts a lightweight HTTP server on the PORT from your .env (3000 by
default) serving the generated files. Stop bun dev first: both servers bind
the same port, and unlike bun dev, bun preview does not reclaim it. It
exits with EADDRINUSE instead.
Development Mode
The bun dev command above runs scripts/dev/orchestrate.ts, which performs the one-shot setup steps (asset sync, image preparation, initial CSS build), then runs the Tailwind CSS watcher (--watch=always) and the development server (scripts/dev.ts) side by side. Changes to templates, markdown, translations, or CSS are detected automatically and the browser reloads. No manual refresh needed.
The CSS watcher recompiles src/css/style.css to src/public/css/style.min.css on every change.
Other Commands
| Purpose | Command |
|---|---|
| SSG with verbose logging | bun scripts/ssg.ts --public ./src/public --dist ./dist --verbose |
| SSG with hreflang links | bun scripts/ssg.ts --public ./src/public --dist ./dist --site-url https://example.com |
| CSS once (minified) | bun css:build |
| CSS watch | bun css:watch |
| Generate sitemap | bun sitemap |
| Generate RSS feed | bun rss |
| Generate search index | bun ssg:search |
| Generate Open Graph cards | bun og:images |
| Format source | bun format (uses reettier) |
| Re-fetch tooling and vendor assets | bun get:pre |
Verifying the Setup
Stop bun dev if it is still running, then:
cd __PROJECT_DIR__
bun ssg
bun preview
If you see "✅ Static Site Generation complete" in the terminal and the preview loads at http://localhost:3000, everything is wired up correctly.
A note about libvips for Open Graph images
bun ssg generates a PNG Open Graph card for every page as part of its pipeline (the og:images step, which runs after the generator finishes and before the sitemap and feeds), and that step shells out to the native libvips library.
bun reeweb:install already installs it for you, by way of bun get:pre, so a project set up as described above needs nothing extra. To install or repair it on its own:
bun get:vips
This supports Windows (prebuilt from GitHub releases), macOS (Homebrew), and Linux (apt/dnf/pacman), and adds vips to your PATH automatically. Prefer bun get:vips over calling bun scripts/cli.ts vips directly: the script pins the version ReeWeb is tested against, and the Windows installer needs a real version number to pick a release asset. Without libvips, bun ssg fails on the og:images step; bun dev does not need it, since it doesn't generate Open Graph images.
Every rendered page needs a non-empty <title>. The og:images step reads it to build that page's card and fails the entire bun ssg run - not just that one card - if a page has no <title> tag, or an empty one (<title></title>). This is intentional: a page with no title is incomplete regardless of Open Graph, so the build treats it as a hard error rather than silently shipping a titleless page. layout.ree already sets one from props.title / props.site_name; a template that renders without going through a layout is the case to watch.
Editor Setup
For VSCode, .vscode/settings.json in this project maps .ree files to a custom language. For other editors, treating .ree as HTML gets you most of the way - the tag syntax ({= }, {#if}, {#each}) is distinct enough that the HTML highlighter ignores it cleanly.