Documentation

Docs

Introduction

What is rune

Philosophy

Why rune

Architecture

Getting Started

Installation

Quick start

Project structure

The dev loop

Tutorial: build an app

1. Create the app

2. Pages & routes

3. Layouts

4. State with Mimir

5. SSR & data

6. API routes

7. Styling

8. Build & deploy

Core Concepts

The application object

Rendering pipeline

Hydration

The build manifest

Configuration (ekko.json)

Permissions

Conventions

Routing

File-based routing

Dynamic routes

The router (useRouter)

Navigation & Link

Guards & redirects

Programmatic routes

Server-Side Rendering

Overview

The ssr() function

Strategies (eager/lazy)

SSR → hydration

Caching & invalidation

SEO

Mimir, state management

Overview

Atoms

Reading & writing

Selectors (derived state)

Subscriptions & the store

SSR & hydration

Persistence & sessions

Patterns & recipes

Pitfalls

Pages & Layouts

Pages

Layouts

Error & not-found

API Routes

Defining routes

Request & response

Middleware

helmet

cors

rateLimit

bodyLimit

validateContentType

csrf

requestId

timeout

errorHandler

httpsRedirect

secureCookies

ipFilter

safePath

Validation & options

Styling & Theming

SCSS

Theming (light/dark)

Asgard integration

No flash (no-FOUC)

Building & Deploying

The build

Static assets

Production deploy

API Reference

ekko:rune

ekko:rune/router

ekko:rune/mimir

ekko:rune/seo

ekko:ssr / css

ekko.json schema

CLI commands

Guides

Rune app from scratch

Recipes

Dark mode

Forms

Data fetching

Authentication

Pagination

FAQ (use cases)

Documentation

Docs

Introduction

What is rune

Philosophy

Why rune

Architecture

Getting Started

Installation

Quick start

Project structure

The dev loop

Tutorial: build an app

1. Create the app

2. Pages & routes

3. Layouts

4. State with Mimir

5. SSR & data

6. API routes

7. Styling

8. Build & deploy

Core Concepts

The application object

Rendering pipeline

Hydration

The build manifest

Configuration (ekko.json)

Permissions

Conventions

Routing

File-based routing

Dynamic routes

The router (useRouter)

Navigation & Link

Guards & redirects

Programmatic routes

Server-Side Rendering

Overview

The ssr() function

Strategies (eager/lazy)

SSR → hydration

Caching & invalidation

SEO

Mimir, state management

Overview

Atoms

Reading & writing

Selectors (derived state)

Subscriptions & the store

SSR & hydration

Persistence & sessions

Patterns & recipes

Pitfalls

Pages & Layouts

Pages

Layouts

Error & not-found

API Routes

Defining routes

Request & response

Middleware

helmet

cors

rateLimit

bodyLimit

validateContentType

csrf

requestId

timeout

errorHandler

httpsRedirect

secureCookies

ipFilter

safePath

Validation & options

Styling & Theming

SCSS

Theming (light/dark)

Asgard integration

No flash (no-FOUC)

Building & Deploying

The build

Static assets

Production deploy

API Reference

ekko:rune

ekko:rune/router

ekko:rune/mimir

ekko:rune/seo

ekko:ssr / css

ekko.json schema

CLI commands

Guides

Rune app from scratch

Recipes

Dark mode

Forms

Data fetching

Authentication

Pagination

FAQ (use cases)

SSR strategies

rune caches the HTML it renders for static SSR routes. The strategy controls when a route's HTML is put into that cache. There are three: eager, background, and lazy. You set a default on createApp and can override per route.

1
2
const app = createApp({ ssr: "eager" /* default for all routes */ });
app.page("/heavy", Heavy, { page: "heavy.tsx", ssr: "lazy" /* override */ });

The three strategies

eager (default)

All eager SSR routes are rendered into the cache at server start, before it begins accepting traffic for them. The very first visitor gets a cache hit.

SSR cache: 48 pages rendered (eager)
EkkoJS SSR listening on http://0.0.0.0:3000
  • Pros: every page is warm instantly; first request is fast.
  • Cons: startup does the work for all pages (slower boot if you have thousands of pages).
  • Use for: content and marketing sites, docs, anything with a bounded page count where you want a warm

cache from the first request. This is the right default for most apps.

background

The server starts listening immediately, then renders background routes into the cache just after start (asynchronously). Early requests to a not-yet-warm route render on demand; once the background pass completes, all are cached.

  • Pros: fast boot; the cache fills without blocking startup.
  • Cons: a brief window after boot where some pages render on first hit.
  • Use for: large sites where eager boot would be too slow, but you still want everything cached soon.

lazy

The route is not pre-rendered. Its HTML is produced on the first request and cached from then on (subject to ttl). Subsequent requests are cache hits.

  • Pros: zero startup cost; only pages that are actually visited are rendered.
  • Cons: the first visitor to each page pays the render cost.
  • Use for: long-tail pages, rarely-visited routes, or when boot time matters more than first-hit latency.

How strategy interacts with route kind

Strategies apply to static routes that have an ssr(), those are the cacheable ones. Dynamic routes (:param, *catch) always serve a shell and render on the client regardless of strategy (their content is per-URL, so there is nothing universal to cache). A static route without ssr() also serves a shell.

has ssr()no ssr()
staticcached per strategyshell
dynamicshell (with ssr() title/head)shell

Per-route overrides

Mix strategies to fit each page:

1
2
3
app.page("/", Home, { page: "index.tsx" }); // eager (the app default)
app.page("/changelog", CL, { page: "changelog.tsx", ssr: "background" });
app.page("/report", Rpt, { page: "report.tsx", ssr: "lazy", ttl: 300 }); // lazy + 5-min TTL

Choosing a default

  • Small/medium content site or docs → eager (warm everything at boot).
  • Hundreds/thousands of pages → background (boot fast, warm shortly after) or lazy (warm on

demand).

  • Mostly dynamic/personalised → strategy matters less; those routes are shells anyway.

What "cached" means here

The cache stores the assembled HTML document keyed by path, with an optional ttl (seconds) and tags. A cache hit serves the stored HTML directly (with a tiny patch to inject the current __user if any). When the cache is stale or absent, the route re-renders. You control invalidation explicitly, see Caching & invalidation.

Next: the client side of the handoff, Hydration.