Configuration

Introduction

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
BLOG_DIR=engineering-notes
OPEN_IDE=vscode
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
BLOG_DIRYesSub-directory under the public dir that holds blog posts, for the RSS/JSON feeds
OPEN_IDENoWhich editor the dev inspector opens

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.

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_languages.ts

Declares the languages your site supports:

export const languages = ["en", "sl"] as const;
export const active_languages = ["sl", "en"] as const;
export const soft_launch_languages: string[] = [];
export const default_language = "sl";

export const language_names: Record<string, string> = {
    en: "English",
    sl: "Slovenian",
};

export const language_locales: Record<string, string> = {
    en: "en-US",
    sl: "sl-SI",
};
ExportPurpose
languagesAll languages with translation files
active_languagesLanguages shown in the language chooser
soft_launch_languagesLanguages that are generated but excluded from sitemaps, feeds, hreflang links, and the language chooser until ready
default_languageLanguage served at the site root (no prefix)

Adding a new language is a one-file change here plus a matching {lang}.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 Pages 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(lang) 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 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.