Documentation
Docs
Introduction
Getting Started
Tutorial: build an app
Core Concepts
Routing
Server-Side Rendering
Mimir, state management
Pages & Layouts
API Routes
Styling & Theming
Building & Deploying
API Reference
Guides
Recipes
FAQ (use cases)
The ssr() function
ssr() is the one server-only hook a page can export. It runs on the server, per render, before the
component is rendered to HTML, and returns metadata and seed state. Its mere presence on a static route is
what turns that route into a fully server-rendered, cached page.
Signature
All fields are optional; returning {} (or nothing) is valid.
title
Sets the document title for this page:
For dynamic routes, ssr() runs per request for the shell, so you can compute a title from the URL if your
setup passes it in (commonly you generate per-entry routes instead, see
Programmatic routes).
head
Raw HTML appended to <head>. This is where compiled CSS, SEO tags, and the no-FOUC script go. Most apps
build head once at startup and pass it to every route's ssr() (or via the route meta), since it is
the same for all pages:
For page-specific SEO (a unique description or OG image), call seo.headTags({ ... }) with overrides for
that page and return it as head:
See SEO.
__atoms , seeding state
The most powerful field. __atoms is a map of atom key → value that the server writes into Mimir
before rendering. The same values are embedded in __EKKO_DATA__, so the client hydrates with exactly
what the server rendered, no flicker, no mismatch.
In a component:
Force and merge
By default a seeded value only fills an atom that has no value yet. To override or deep-merge, use the
directive form (produced by Mimir's createStore):
{ "__force": true, "__value": v }, overwrite the atom withv.{ "__merge": true, "__value": v }, deep-mergevinto the existing object value.
The hydration side applies the same rules (mimir.initStore), so server and client agree. Full treatment in
Mimir → SSR & hydration.
What ssr() is not for
- Not per-user data on a cached static route. A static route's render is cached and shared across
users, so do not seed user-specific values into a cached page's __atoms, they would leak the first
user's state to everyone. For per-user data, use a dynamic/shell route or fetch after hydration.
- Not a place for client-only APIs.
ssr()runs on the server;window/documentdo not exist there. - Not the component.
ssr()returns metadata; thedefaultexport renders the UI.
Where it runs in the lifecycle
request → match route → ssr() → seed Mimir → renderToString(layout(page)) → htmlShell → cache → respondNext: when the cache is populated, Strategies.