Setting Up a Blog

ReeWeb supports blog content through markdown files. Each post is a .md file in src/public/blog/, and SSG renders them to HTML, generates an RSS feed, and includes them in the sitemap. This recipe walks through the complete setup.

Directory Structure

src/public/blog/
├── en-us.json             ← Blog section translations
├── index.ree              ← Blog index page
├── index.ts               ← Blog index data loader
├── 01_hello-world.md      ← Blog post (markdown)
├── 02_second-post.md
└── images/                ← Blog-specific images

Step 1: Create the Blog Index Page

Create src/public/blog/index.ree:

{#layout("layout") }

<h1>{_ ui.blog_title }</h1>

{#each props.posts as post }
    <article>
        <h2><a href="{~ localized_path(post.canonical_path) }">{= post.title }</a></h2>
        <time datetime="{= post.date_iso }">{= post.date_display }</time>
        {#if post.excerpt }
            <p>{= post.excerpt }</p>
        {/if}
        <a href="{~ localized_path(post.canonical_path) }">Read more →</a>
    </article>
{/each}

Step 2: Create the Data Loader

Create src/public/blog/index.ts that collects posts from markdown files:

import { readFileSync, readdirSync } from "fs";
import { join } from "path";
import { parse_frontmatter, template_to_canonical } from "$lib/static_site";

export async function load_template_data(): Promise<Record<string, any>> {
    const blog_dir = join(import.meta.dir, "..", "..", "public", "blog");
    let entries = readdirSync(blog_dir)
        .filter((f) => f.endsWith(".md"))
        .sort();

    const posts = entries
        .map((file) => {
            const content = readFileSync(join(blog_dir, file), "utf-8");
            const { data } = parse_frontmatter(content);
            const canonical_path = template_to_canonical(`blog/${file}`);
            return {
                title: data.title || "Untitled",
                date_iso: data.date || "",
                date_display: data.date
                    ? new Date(data.date).toLocaleDateString("en-us", {
                            year: "numeric",
                            month: "long",
                            day: "numeric",
                        })
                    : "",
                excerpt: props.excerpt || "",
                canonical_path,
            };
        })
        .sort((a, b) => b.date_iso.localeCompare(a.date_iso)); // newest first

    return { posts };
}

Step 3: Create a Blog Post

Create src/public/blog/01_hello-world.md:

---
title: "Hello, World!"
date: 2026-06-01
excerpt: "Welcome to the blog! This is our first post."
layout: "layout"
---

Welcome to the ReeWeb blog! This is our first post, written in markdown.

## Features

- Syntax highlighting for code blocks
- Auto-generated heading IDs
- Tailwind classes on all elements
- External links open in new tabs

```typescript
const greeting = "Hello from ReeWeb!";
console.log(greeting);
```

The ordering prefix `01_` is stripped from the URL - the canonical path becomes `/blog/hello-world`.

<a name="step-4-generate-the-rss-feed"></a>
## Step 4: Generate the RSS Feed

Run the RSS generator:

```bash
bun rss
```

This runs `scripts/generate_rss.ts`, which scans `src/public/blog/` for markdown files, extracts frontmatter, and writes `feed.xml` and `feed.json` next to the generated blog index, at `dist/<BLOG_DIR>/` (so `dist/blog/feed.xml` with the default `BLOG_DIR=blog`). The sitemap generator can also be run separately:

```bash
bun sitemap
```

Both are included automatically via `bun ssg` and can be run independently.

<a name="step-5-add-translations"></a>

## Step 5: Add Translations

Create `src/public/blog/en-us.json`:

```json
{
    "blog_title": "Blog"
}
```

And `src/public/blog/sl-si.json`:

```json
{
    "blog_title": "Blog"
}
```

<a name="full-ssg-with-rss-and-sitemap"></a>

## Full SSG with RSS and Sitemap

For production generation that includes everything:

```bash
bun ssg
bun rss
bun sitemap
```

Or combine into a single script in your `package.json`:

```json
"ssg:full": "bun ssg && bun rss && bun sitemap"
```