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)

The application

Everything starts with createApp. It returns an application object you register routes and middleware on, then start. This page is the reference for that object and the options it takes.

1
2
3
4
5
6
7
import { createApp } from "ekko:rune";
 
const app = createApp(options);
app.page("/", HomeModule, { page: "index.tsx" });
app.api("GET", "/api/health", (_req, res) => res.json({ ok: true }));
app.use(myMiddleware);
const handle = app.start(); // begins listening

createApp(options)

OptionTypeDefaultPurpose
portnumber3000Listening port.
hoststring"0.0.0.0"Bind address. Pair with a firewall/nginx in prod (see deploy).
manifestobjectnullThe build manifest from readManifest(); lets the server emit the right client <script>/modulepreload tags.
layoutsobjectnullThe layout tree (root + nested). See Layouts.
notFoundComponentnullThe 404 component, rendered for unmatched routes.
errorComponentnullThe error component for render failures.
ssr"eager" | "background" | "lazy""eager"Default SSR strategy for static, SSR-enabled routes. See Strategies.
langstring"en"<html lang>.
staticstringA directory served verbatim.
staticPrefixstring"/static"URL prefix for static. The examples use /assets.
tls, http2Passed to the underlying server (usually you terminate TLS at nginx instead).
maxBodySize, maxWsMessageSizenumberRequest/WebSocket size limits.
seoobjectCarried for convenience; SEO tags are produced via createSEO(...).headTags() and injected through page head.

Methods on the app object

app.page(path, component, meta?)

Registers a route. component is the page module (it may be the module namespace with a default export and optional ssr, or the component directly). meta carries:

  • page , the page key into the manifest (e.g. "index.tsx"), so the server knows which client chunk to load.
  • title , a default document title.
  • head , extra HTML for <head> (compiled CSS, SEO tags, no-FOUC script).
  • ssr , a per-route strategy override ("eager" | "background" | "lazy").
  • ttl, tags , SSR cache controls (see Caching).
  • guard , a client redirect rule.

Returns app for chaining.

app.pages(dir, components, metas)

A bulk helper: scanRoutes(dir) + register each discovered route whose pattern/file you provide a component for. Most apps loop over scanRoutes themselves (it is more explicit); pages() is the shorthand.

app.api(method, path, [opts], handler)

Registers an API route. method is "GET" | "POST" | "PUT" | "DELETE" | "PATCH". The handler gets (req, res); returning a value auto-sends it (objects as JSON, else as text). See API routes.

app.use(middleware)

Adds middleware to the underlying server (runs for every request). See Middleware.

app.layout(fn)

Sets a single root layout function (the layouts option is the richer, tree-based form).

app.invalidate(pathOrTag)

Drops cached SSR HTML and re-renders. "*" clears everything; a path ("/blog") targets one route; any other string is treated as a tag and clears every cached page carrying it. See Caching & invalidation.

app.start()

Begins listening and returns { server, url, stop, invalidate, cache }. On start it:

  1. Mounts static serving for the client bundle at /_ekko (from .ekko/build/client, long-cache + immutable).
  2. Opens the HMR WebSocket at /__ekko_hmr and starts watching the build token.
  3. Registers your API routes (with automatic 405 Method Not Allowed for declared paths).
  4. Builds the client route table (__routes) and finds the shared layout chunk for hydration.
  5. Registers a GET handler per page, cached SSR for static SSR routes, a shell for the rest.
  6. Registers the notFound catch-all and any static directory.
  7. Eagerly renders the eager SSR routes into the cache, then prints the listening URL.
  8. Schedules background SSR routes to render just after start.

A minimal but complete entry

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import "@ekko/react";
import "@ekko/react-dom/server";
import { createApp, scanRoutes, readManifest } from "ekko:rune";
import { compileSass } from "ekko:ssr/css";
import { readText } from "ekko:fs";
 
import RootLayout from "./pages/layout";
import NotFound from "./pages/not-found";
import ErrorPage from "./pages/error";
import * as Home from "./pages/index";
 
const head = `<style>${compileSass(readText("styles/global.scss"))}</style>`;
 
const app = createApp({
port: Number(Ekko?.env?.get?.("PORT")) || 3000,
manifest: readManifest(),
layouts: { "": { layouts: [{ render: RootLayout }] } },
notFound: NotFound,
error: ErrorPage,
ssr: "eager",
static: "./static",
staticPrefix: "/assets",
});
 
const modules: any = { "/": Home };
for (const r of scanRoutes("pages")) {
const mod = modules[r.pattern];
if (mod) app.page(r.pattern, mod, { page: r.pageKey, head });
}
 
app.start();

Next: what actually happens on a request, The rendering pipeline.