Configuration
ReeWeb has very little to configure. Everything that varies between environments lives in .env and is read directly from Bun.env. Everything that varies between projects lives in TypeScript files under config/ that you edit like any other source file.
The .env File
Copy the example file:
cp .env.example .env
The full set of variables:
SITE_URL=https://example.com
BASE_URL=/
PORT=3000
PUBLISHER_PORT=3011
PUBLISHER_PREVIEW_PORT=3012
PUBLISHER_BRANCH=main
BLOG_DIR=engineering-notes
OPEN_IDE=vscode
# MCP_ENABLE_TEMPLATE_RENDER=true # enables MCP template/page execution
# MCP_ENABLE_MUTATIONS=true # enables MCP operations that write files
| Variable | Required | Purpose |
|---|---|---|
SITE_URL | Yes | Full site URL, used for canonical/hreflang links and the feeds/sitemap |
BASE_URL | Yes | Base URL path the site is served from (/ for a domain root) |
PORT | Yes | Local server port for the dev server and preview server |
PUBLISHER_PORT | No | Port for the Publisher dashboard |
PUBLISHER_PREVIEW_PORT | No | Port the Publisher's candidate preview serves on |
PUBLISHER_BRANCH | No | Branch whose clean commits are eligible for Publisher release rendering |
BLOG_DIR | No | Sub-directory under the public dir that holds blog posts, for the RSS/JSON feeds. RSS generation is skipped (not an error) when unset |
OPEN_IDE | No | Which editor the dev inspector opens |
MCP_ENABLE_TEMPLATE_RENDER | No | Explicit opt-in for MCP tools that execute local templates, data loaders, or full page rendering |
MCP_ENABLE_MUTATIONS | No | Explicit opt-in for MCP tools that write translations, locales, or dist/ |
The required variables are strict and env-only: each is read from .env (no fallback to package.json or a hidden code default), and the script exits with an error if the value is missing. Where a script accepts an equivalent CLI flag (--site-url, --base-url, --port, --blog-dir), the flag takes precedence over the environment value. This keeps every configurable default visible in one place - your .env - rather than buried in code.
SITE_URL- when set, the SSG generates<link rel="alternate" hreflang="...">tags pointing to absolute URLs for each language variant, required by Google for multi-language SEO. Read by thessg,sitemap, andrssscripts.BASE_URL- the URL path the site is mounted at;/for a domain root, or e.g./docs/when served from a sub-path.PORT- the port the dev server (bun dev) and preview server (bun preview) listen on.BLOG_DIR- the folder undersrc/public/scanned for blog posts when generating the RSS and JSON feeds.
OPEN_IDE is the one optional variable: it selects which editor the dev inspector's "open in editor" action launches - one of vscode, zed, nvim, sublime, or idea. It is not required to run the dev server or generate the site; it is only consulted when you use that inspector action, which reports a clear error if OPEN_IDE is unset. See Dev Inspector - Editor Configuration.
The MCP variables are also optional and should normally be supplied only to the local MCP process. See MCP Server for the security model and tool reference.
SSG Options
The bun ssg script accepts several flags:
bun scripts/ssg.ts --public ./src/public --dist ./dist --base-url / --site-url https://example.com --verbose
| Option | Default / Source | Description |
|---|---|---|
--public | ./src/public | Source directory with .ree templates |
--dist | ./dist | Output directory for static HTML |
--base-url | BASE_URL (env) | Base URL for the site |
--site-url | SITE_URL (env) | Full site URL for canonical/hreflang |
--verbose | false | Log each rendered file |
--base-url and --site-url have no code default: when the flag is omitted they read BASE_URL / SITE_URL from .env, and the script exits with an error if neither the flag nor the environment variable is set.
config/supported_locales.ts
Declares the locales your site supports (BCP 47 codes):
export const locales = ["en-us", "sl-si"] as const;
export const active_locales = ["sl-si", "en-us"] as const;
export const soft_launch_locales: string[] = [];
export const default_locale = "sl-si";
export const locale_names: Record<string, string> = {
"en-us": "English",
"sl-si": "Slovenian",
};
export const locale_aliases: Record<string, string> = {};
| Export | Purpose |
|---|---|
locales | All locales with translation files |
active_locales | Locales shown in the locale chooser |
soft_launch_locales | Locales that are generated but excluded from sitemaps, feeds, hreflang links, and the locale chooser until ready |
default_locale | Locale served at the site root (no prefix) |
locale_aliases | Aliased locales share their target locale's UI strings (one level only) |
Adding a new locale is a one-file change here plus a matching {locale}.json translation file.
config/redirects.ts
Declares URL redirects (301 by default, 302 for temporary):
export const redirects: { from: string; to: string; status?: 301 | 302 }[] = [
{ from: "/old-page", to: "/new-page" }, // 301
{ from: "/promo", to: "https://example.com/landing", status: 302 }, // 302
];
Redirects are validated in two phases by lib/redirects.ts:
- Schema validation - checks
fromstarts with/, has no file extension,tois non-empty - Collision check - after rendering, verifies
fromdoesn't collide with a page or asset
Emitted as:
dist/_redirects- Cloudflare Workers Static Assets formatdist/{from}/index.html- HTML meta-refresh stubs (fallback for other hosts)
config/pagination.ts
Declares which routes get paginated output and how. The default config enables pagination for the blog:
export const pagination: PaginationConfig = {
enabled: true,
per_page: 10,
path_segment: "", // /blog/2/ - no untranslated "page" in the URL
show_when_single_page: false,
always_show_prev_next: true,
variant: "full", // "full" or "simple"
routes: [
{ route: "blog" },
],
};
| Option | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Master switch. false disables pagination entirely. |
per_page | number | 10 | Default items per page (routes can override). |
path_segment | string | "" | URL segment before the page number. "" gives /blog/2/; "page" gives /blog/page/2/. |
show_when_single_page | boolean | false | Show pagination element when all results fit on one page. |
always_show_prev_next | boolean | true | Always render disabled Previous/Next buttons. |
variant | "full" | "simple" | "full" | Which pagination component variant is used. |
routes | PaginationRoute[] | [] | Routes to paginate. Each can override per_page, set source ("markdown" or "loader"), and sort order. |
Records are collected from markdown files by default. To supply records from an external source, export load_records(locale) from the route's index.ts. See Pagination.
package.json Scripts
The scripts you'll use most:
| Script | Purpose |
|---|---|
bun dev | Development server + Tailwind CSS watcher (concurrent) |
bun ssg | Generate the static site to dist/ |
bun preview | Serve dist/ locally |
bun cf:share | Share dist/ temporarily through a public Wrangler tunnel |
bun cf:deploy | Deploy dist/ to Cloudflare Workers |
bun css:build | Build minified Tailwind CSS from src/css/style.css |
bun css:watch | Watch and rebuild CSS during development |
bun format | Format all source files (reettier) |
bun sitemap | Generate sitemap.xml |
bun rss | Generate RSS feed |
For hreflang links, pass --site-url directly:
bun scripts/ssg.ts --public ./src/public --dist ./dist --site-url https://example.com --verbose
How Bun Loads .env
Bun reads .env files automatically at startup - there's no dotenv dependency to install. Variables become available on Bun.env.NAME.