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)

Project structure

A rune project is a set of conventional folders. The conventions are load-bearing, the framework derives routes, layouts, and the build from the layout on disk, so it pays to know what each folder is for.

my-app/
  server.tsx              # entry: wires the app together and calls app.start()
  ekko.json               # project config (type "run", permissions, dependencies)
  ekko.lock               # resolved dependency versions from the store
  pages/                  # routes, one module per URL
    index.tsx             #   /
    about.tsx             #   /about
    blog/
      index.tsx           #   /blog
      [slug].tsx          #   /blog/:slug
    layout.tsx            #   root layout (the shell)
    not-found.tsx         #   404 page
    error.tsx             #   error page
  components/             # reusable UI, not routes
  atoms/                  # Mimir atoms (shared, reload-tolerant state)
  lib/                    # plain TS modules (config, helpers, theme objects)
  styles/
    global.scss           # the design system; compiled at server start
  static/                 # files served verbatim under /assets (images, fonts, svg)
  content/                # data sources (e.g. docs markdown + generated data)
  _build/                 # local build scripts (e.g. docs converter)
  .ekko/build/            # GENERATED by `ekko build --client` (client bundle + manifest)

The folders that matter, and why

pages/

The router's source of truth. Every .tsx/.jsx/.ts/.js file is a route, except the convention files (layout, loading, error, not-found, and their _-prefixed variants), which have special meaning. File names map to URLs:

  • index.tsx → the folder's root (pages/index.tsx/, pages/blog/index.tsx/blog)
  • [param].tsx → a dynamic segment (:param)
  • [...rest].tsx → a catch-all (*rest)
  • (group)/ → a grouping folder that is stripped from the URL

Full rules in File-based routing.

A page module exports a default component and may export ssr() (server-only metadata + seed state). See Pages.

Import code without the extension. Files on disk carry .tsx/.ts, but you import them as ./pages/about, not ./pages/about.tsx. Ekko owns resolution and rejects extensioned specifiers at run. See Why extensionless? for the reasoning.

components/

Plain React components that are not routes. Nothing magic, import and use them in pages or other components.

atoms/

By convention, your Mimir atoms live here, one small module per concern (theme, cart, UI flags). Atoms are the right home for any state that must outlive a navigation or a refresh. See Mimir.

lib/

Ordinary modules: site configuration (site.ts with the name, URL, nav links), theme objects for @ekko/asgard (theme.ts), and helpers. Imported by server.tsx and your pages.

styles/

global.scss is your design system, typically CSS custom properties on :root (light) and .dark. It is compiled to CSS at server start with compileSass(readText("styles/global.scss")) and injected into the document head. See Styling.

static/

Anything here is served verbatim. With static: "./static", staticPrefix: "/assets", a file at static/logo.svg is available at /assets/logo.svg. Good for images, fonts, and SVG diagrams.

content/

A convention for data sources. In a docs site, content/docs-src/*.md is the markdown you edit and content/docs/docs.data.ts is the generated, embedded data the app imports. Computed once, served from memory.

.ekko/build/

The output of ekko build --client: the hashed client chunks under .ekko/build/client/ and the manifest.json the server reads. Do not edit by hand; rebuild it. It is shipped to production as part of the app bundle (the prod box does not rebuild client code).

server.tsx , the one file that ties it together

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import "@ekko/react"; // register the JSX runtime
import "@ekko/react-dom/server"; // use React's renderToString for SSR
import { createApp, scanRoutes, readManifest } from "ekko:rune";
import { compileSass } from "ekko:ssr/css";
import { readText } from "ekko:fs";
import { createSEO } from "ekko:rune/seo";
 
// 1. build the head: compiled CSS, a no-FOUC script, SEO tags
const css = compileSass(readText("styles/global.scss"));
const seo = createSEO({ site: { name: "My App", url: "https://example.com" } });
const head = `<style>${css}</style>${seo.headTags()}`;
 
// 2. create the app
const app = createApp({ port: 3000, manifest: readManifest(), layouts, ssr: "eager",
static: "./static", staticPrefix: "/assets", error: ErrorPage, notFound: NotFound });
 
// 3. register pages and APIs
for (const r of scanRoutes("pages")) { /* app.page(...) */ }
app.api("GET", "/api/health", (_req, res) => res.json({ ok: true }));
 
// 4. listen
app.start();

Every line here is explained in its own chapter, The application for createApp, Routing, SSR, API routes, SEO, and Styling.

Next: The dev loop.