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
VariableRequiredPurpose
SITE_URLYesFull site URL, used for canonical/hreflang links and the feeds/sitemap
BASE_URLYesBase URL path the site is served from (/ for a domain root)
PORTYesLocal server port for the dev server and preview server
PUBLISHER_PORTNoPort for the Publisher dashboard
PUBLISHER_PREVIEW_PORTNoPort the Publisher's candidate preview serves on
PUBLISHER_BRANCHNoBranch whose clean commits are eligible for Publisher release rendering
BLOG_DIRNoSub-directory under the public dir that holds blog posts, for the RSS/JSON feeds. RSS generation is skipped (not an error) when unset
OPEN_IDENoWhich editor the dev inspector opens
MCP_ENABLE_TEMPLATE_RENDERNoExplicit opt-in for MCP tools that execute local templates, data loaders, or full page rendering
MCP_ENABLE_MUTATIONSNoExplicit 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 the ssg, sitemap, and rss scripts.
  • 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 under src/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
OptionDefault / SourceDescription
--public./src/publicSource directory with .ree templates
--dist./distOutput directory for static HTML
--base-urlBASE_URL (env)Base URL for the site
--site-urlSITE_URL (env)Full site URL for canonical/hreflang
--verbosefalseLog 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> = {};
ExportPurpose
localesAll locales with translation files
active_localesLocales shown in the locale chooser
soft_launch_localesLocales that are generated but excluded from sitemaps, feeds, hreflang links, and the locale chooser until ready
default_localeLocale served at the site root (no prefix)
locale_aliasesAliased 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:

  1. Schema validation - checks from starts with /, has no file extension, to is non-empty
  2. Collision check - after rendering, verifies from doesn't collide with a page or asset

Emitted as:

  • dist/_redirects - Cloudflare Workers Static Assets format
  • dist/{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" },
    ],
};
OptionTypeDefaultDescription
enabledbooleantrueMaster switch. false disables pagination entirely.
per_pagenumber10Default items per page (routes can override).
path_segmentstring""URL segment before the page number. "" gives /blog/2/; "page" gives /blog/page/2/.
show_when_single_pagebooleanfalseShow pagination element when all results fit on one page.
always_show_prev_nextbooleantrueAlways render disabled Previous/Next buttons.
variant"full" | "simple""full"Which pagination component variant is used.
routesPaginationRoute[][]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:

ScriptPurpose
bun devDevelopment server + Tailwind CSS watcher (concurrent)
bun ssgGenerate the static site to dist/
bun previewServe dist/ locally
bun cf:shareShare dist/ temporarily through a public Wrangler tunnel
bun cf:deployDeploy dist/ to Cloudflare Workers
bun css:buildBuild minified Tailwind CSS from src/css/style.css
bun css:watchWatch and rebuild CSS during development
bun formatFormat all source files (reettier)
bun sitemapGenerate sitemap.xml
bun rssGenerate 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.