Routes And Render Modes - developerz-ai/ultimate GitHub Wiki

Routes and render modes

A route is a URL + render mode + metadata + offline strategy. Render mode is a per-route property. SEO is enforced by the build, not described in a guide.

As of 2026-08. Stable API β€” semver from here (Upgrading).

The canonical shape

export const config = defineRoute({
  render:     'isr',                  // static | isr | ssr | stream
  revalidate: { tags: [tag.post] },
  prerender:  () => db.posts.slugs(),
  offline:    'precache',             // precache | runtime | network-only
  hydrate:    'visible',              // idle | visible | interaction | never
  budget:     { js: '40kb' },
  meta:       ({ post }) => ({ title: post.title, description: post.excerpt,
                               og: { image: post.cover }, ld: ld.Article(post) }),
});
Aspect Rule
Projects to router entry, prerender list, sw.js precache/runtime rule, sitemap + RSS row, <head> + JSON-LD, per-route budget check
Owns render mode, hydration timing, metadata, offline strategy
Never touch the DB directly, hold business logic, or omit meta.description in site/ β€” that is a build error

post β€” a page URL that also answers POST

defineRoute({ render: 'ssr', …, post: 'unsubscribeOnboarding' }) (As of 22.6.0) mounts POST at the page's own path, answered by that action β€” its policy, idempotency and redirect β€” with the URL's query merged over the posted fields (the query wins a shared name: it is the URL the server minted). The RFC 8058 case: the mail client POSTs List-Unsubscribe=One-Click to /correos/baja?t=… and the action receives { t, 'List-Unsubscribe': 'One-Click' }; the page's GET stays the confirm page, and its form can post to itself with no JavaScript. Declare optional input keys for what the client posts. Not an export name, or on render: 'static': X_ROUTE_POST_INVALID at declaration; naming no registered action: the same code at boot.

navigation β€” how the client router treats this page

Only on a surface listed in app.config.ts navigation.client (Client navigation); anywhere else X_ROUTE_NAVIGATION_INVALID at boot, and any value but these two at declaration.

Value The router The server, before load
absent swaps it in on a click; never fetches it early answers a prefetch 204, runs nothing
'prefetch' also fetches it on hover/focus answers a prefetch like a visit. Only for a GET that does nothing but render
'document' always a real document load, and the page carries no router (no script, no metas, no bytes charged) answers a soft visit 204 + x-ultimate-location, runs nothing; the browser's load runs it once. For a GET that records something: an open, a download, a token consumed β€” and for a 0b page

The descriptor

route is the one primitive whose faΓ§ade is a normalized descriptor rather than a set of projection methods, because a route declares no behaviour to project. It is read by the router, the prerenderer, the sitemap and the budget check β€” and is designed to be read by sw.js generation, which no build calls yet (#362). The descriptor's job is to hand all of them one shape so none of them branches. There is no .describe() on a route either β€” describePages() is the one route list.

Member Is Rule
kind: 'route' the brand lets the registry reject a non-route export. isRouteConfig(value) is the guard
meta(data) the <head> producer always returns a promise. A synchronous declared meta is wrapped, and a meta that throws synchronously becomes a rejection β€” so await config.meta(data) is the one way to fail as well as the one way to succeed, and no consumer branches on a thenable
budget the per-route limits always an object, {} when undeclared, rather than a second undefined-check at every call site
render revalidate prerender offline hydrate policy cache the declaration, carried through unchanged. Optional keys are omitted, never set to undefined
the whole object Object.freezed no consumer can mutate the route another consumer is about to read

The always-present budget does not weaken the site/ JS-budget check. budget is always an object, but its fields stay optional β€” so budget.js === undefined still means "this route declared no JS budget", and a site/ route that opts into any hydrate other than never without one is X_ROUTE_MODE_INVALID, exactly as before the normalization. Measuring the declared numbers against the built output is the separate concern in Budgets, below.

Validation runs at module evaluation. defineRoute checks the shape and the mode-local invariants immediately, so a bad route fails at build time rather than on the first request in production.

Checked Where Code
offline present and a known strategy defineRoute X_ROUTE_OFFLINE_MISSING
meta is a function defineRoute X_ROUTE_META_MISSING
mode-local: known render and hydrate; static with a policy or a revalidate; isr with a policy or with no trigger; ssr with a prerender; cache on any mode but ssr, or a public/immutable cache on a gated route defineRoute X_ROUTE_MODE_INVALID
surface-dependent: mode allowed on the surface; site/ hydration without budget.js; stream with no <Suspense>; prerender on a non-prerenderable mode registerRoute X_ROUTE_MODE_INVALID
the config came from defineRoute and not straight from the author registerRoute X_ROUTE_UNNORMALIZED
two files claiming one URL registerRoute X_ROUTE_DUPLICATE

The split is about what is knowable, not about strictness: everything decidable from the config alone is decided at import; the rest needs the file's surface, which only the route table knows.

Four render modes

Mode Behavior Use
static built once, served as a file marketing, docs
isr static + background regen on tag/TTL catalogs, profiles
ssr per-request full render fresh SEO pages
stream static shell flushed instantly, holes streamed default for app pages

A served static page, As of 2026-09: a matching If-None-Match is a bodiless 304, and the container renders each page once per locale and answers from memory after β€” no load, no render β€” for a page with no dynamic segment, asked for with no query string, that answered 200. x dev renders per request. No other mode is kept this way. What is kept is bounded twice β€” 1,024 entries and 32 MiB of bodies (STATIC_MEMO_MAX_BYTES) β€” because the request origin is in the key when the app declares none; past either bound a page is rendered per request.

Surface Default Allowed
site/ static static, isr, ssr
app/ stream stream, ssr
api/ n/a no rendering at all

A mode outside a surface's allowed set is a build error, not a runtime fallback. Surfaces and their boundaries: Project layout.

spa was the fifth mode and was deleted in 6.0.0. It served <body><div id="x-root"></div></body> for the framework's whole history β€” 200, correct headers, blank page β€” because renderSpa preloaded a chunks array no build ever produced and never read the route's component. render: 'spa' is now X_ROUTE_MODE_INVALID at defineRoute time; the migration is one line, render: 'ssr', in Upgrading.

What a mode's headers are, and who has the last word

A render mode states the mode's intent; @ultimat3/http's cache-headers stage makes the final answer. As of 2026-08-23 that stage REVIEWS a cache-control the mode already wrote instead of standing down, which is what made the rule below reachable for a page at all.

The request What it gets, whatever the mode wrote
anonymous the mode's own intent, plus the key dimensions it forgot: vary: accept-language, cookie, x-timezone
carrying an identity private, max-age=0 β€” never public, never s-maxage, and never bare freshness: max-age, must-revalidate or proxy-revalidate without private/no-store is an offer a shared cache may take (RFC 9111 Β§3), so it is rewritten too. meta.auth is only 'public' | 'required', so the page that greets a signed-in visitor by name is a 'public' route whose own header offered it to a CDN for 30 seconds
a content-addressed URL (immutable) left alone. immutable asserts the body is a function of the URL, which is what an island chunk is

A route's own cache, on ssr

As of 2026-09-24. An ungated ssr page is offered to a CDN for 30 seconds (public, max-age=0, s-maxage=30, stale-while-revalidate=300). A page can be public and still personal: a recipient landing addressed by a capability token in its path, or a verification page that must never answer with an old result. cache replaces that default:

export const config = defineRoute({
  render: 'ssr',
  offline: 'network-only',
  cache: 'no-store',                  // or a CacheHint: { mode: 'public', sMaxAgeSeconds: 5 }
  meta: ({ t }) => ({ title: t('verify.title'), description: t('verify.description') }),
});
'no-store' private, no-store, the answer a gated page already gets. It carries the principal scope like every private document
a CacheHint @ultimat3/http's own type, without tags, emitted by cacheControl(). Its vary joins the defaults. The router checks its ages at mount (X_CONFIG_INVALID)
which modes ssr only. static and isr are hashed or TTL'd documents whose headers are the mode itself. A stream is always private, no-store. On any of them cache is X_ROUTE_MODE_INVALID
a gated route may narrow its cache ('no-store', { mode: 'private', … }), never widen it. public or immutable is X_ROUTE_MODE_INVALID
a signed-in visitor the table above still applies. A declared public becomes private, max-age=0 for them

An isr entry is keyed by the negotiated locale. The store key is isrKey(url, locale) β€” the path in its one spelling (below), the reserved __x_locale parameter, then the query the route declared (revalidate.query, below) with its params sorted. Without it, an app shipping two locales served visitor 2 the document rendered for visitor 1, for the whole TTL, and told the CDN to do the same. The time zone is deliberately not a dimension β€” a locale set is declared and bounded, a zone list is not β€” so a date on an isr page belongs in a zone the page itself names, or the page belongs in ssr.

An isr page is one entry however its URL is spelled. The router answers /blog//abc/, /blog/%61bc and /blog/abc with the same route and the same slug, so the store keys on the address it matched, never on the pathname as written: empty segments and the trailing slash dropped, each segment decoded and re-encoded one way (@ultimat3/http's canonicalPath, built from the functions the router itself segments and decodes with). As of 2026-10-11.

Request Stored under Why
/blog/abc, /blog/abc/, /blog//abc, //blog/abc, /blog/%61bc, /blog/./abc /blog/abc one route, one slug
/blog/caf%c3%a9, /blog/caf%C3%A9, /blog/cafΓ© /blog/caf%C3%A9 hex case and raw non-ASCII are spellings
/blog/a%2fb, /blog/a%2Fb /blog/a%2Fb an encoded slash is part of the segment (slug is a/b), never a separator: /blog/a/b is another address, and a 404 for /blog/[slug]
/en/blog/abc, /en//blog/%61bc/ /en/blog/abc the locale prefix is kept; a locale's home is /en/
/shop/blog/abc under basePath: '/shop' /blog/abc the route table has never heard of the mount
  • load, meta and the page are handed that same address as url (base path included), so the document's canonical is /blog/abc whichever spelling arrived first. A static page is rendered at its address too, as the export writes it; ssr and stream keep the URL as the visitor wrote it.
  • Nothing is redirected, and no status changes: a spelling the router answered with a 200 is still a 200, with the canonical page's cache-control, purge keys and canonical link. Redirect at the ingress if you want one URL in a browser's address bar.
  • A key no route owns is never stored. An entry takes its ttl and its tags from its route, so stored without one it was fresh for ever and on no tag edge. isrController().serve() renders it for the request that asked, private, no-store, and logs isr.key.unowned once. The same holds for a request the router matched as one route while its address reads back as another (//en/x is the /[lang]/x route; /en/x is the /x route in English): answered, never stored.
  • Releases through 27.8.0 keyed on the raw pathname (Known Gaps).

A locale-prefixed isr page is its route's page. The key keeps the prefix β€” /en/blog/a?__x_locale=en is the document rendered for that URL β€” and the route it belongs to is found on the path with the routed prefix removed (unlocalizedPath), as the router matches it. So /en/blog/a takes /blog/:slug's revalidate.ttl (s-maxage included), joins its revalidate.tags, and is regenerated in its own locale: a tag bust or the TTL expires every locale's copy of a page, stored 404s included. A page whose truth changes with the clock (publishedAt <= now) is written by nothing when the instant passes, so no tag is busted β€” it needs a ttl, and the ttl alone is enough in every locale. Releases through 27.6.1 matched no route for a prefixed page (Known Gaps): tag-only s-maxage=60, no tag edge, fresh for the life of the process.

An isr regeneration holds its page for at most 30 s (DEFAULT_ISR_REGENERATE_DEADLINE_MS; isrController({ regenerateDeadlineMs }), a whole number β‰₯ 1) As of 2026-10-02. A render that never settles used to pin its page for the life of the process: a missed page hung every later request, a stale one was served stale forever. Past the deadline the next request renders again; the hung render is not cancelled, and its late result is dropped if a newer one has started. A stored path takes its TTL and tags from the most specific route that matches it β€” segment by segment, static beats :param beats *catch-all, as the request router resolves it β€” so /docs/7 is /docs/:id's, never /docs/*path's. A zero-length ttl ('0s', 0) is no trigger at all: X_ROUTE_MODE_INVALID.

revalidate β€” what an invalidation does, how old a copy may be, what the page varies on

As of 2026-10-10. Three optional keys beside tags and ttl, each refused at defineRoute when the controller could not act on it (X_ROUTE_MODE_INVALID).

import { tag } from '@ultimat3/cache';
import { defineRoute } from '@ultimat3/render';

declare const posts: { page(n: number): Promise<{ readonly titles: readonly string[] }> };

export const config = defineRoute({
  render: 'isr',
  revalidate: {
    tags: [tag('post')],
    ttl: '10m',
    onInvalidate: 'purge', // 'stale' (default) | 'purge'
    maxStale: '1h',        // past ttl + 1h a copy is not an answer; 0 (purge's default) is never
    query: ['page'],       // the only query parameters in the key; [] for none
  },
  offline: 'runtime',
  load: ({ url }) => posts.page(Number(new URL(url).searchParams.get('page') ?? '1')),
  meta: ({ url }) => ({ title: 'Blog', description: 'Every post, newest first.', canonical: url }),
});
Key Default What it decides
onInvalidate: 'stale' yes a tag bust marks the stored page stale; the next request is answered that copy, however old, while a fresh one renders behind it (x-ultimate-isr: stale). Right for a price list
onInvalidate: 'purge' β€” a tag bust deletes every stored page of the route carrying the tag β€” each locale, each keyed query, stored 404s included β€” and evicts any render of them in flight, so the next request renders and waits. For content that must come down (a withdrawn article, a corrected legal text). Needs tags and ttl (X_ROUTE_MODE_INVALID without either). The response carries no stale-while-revalidate, and its s-maxage is what is left of the ttl
maxStale unbounded; 0 under 'purge' how long past its ttl a stored page may still be answered stale: a duration, or 0 for never. Older, it is dropped and the request waits for a render. Also the stale-while-revalidate a shared cache is told (a day when omitted, absent at 0). Needs ttl
query the whole query string the query parameters the page varies on. Only these are in the stored page's key and in the url / query that load, meta and the page receive; one value each (the first), sorted. [] is "none" and what most pages want

Declare query on every isr route. Omitted, the key carries whatever the visitor sent: /blog?x=1, /blog?x=2, … are each a render and a stored page (the store holds 1,000), which is a cost-amplification vector, and utm_* / fbclid / gclid split one page into many β€” each of which a withdrawal has to reach. x verify names every isr route without a declaration (the seo step's warnings), and the server logs isr.query.undeclared once per route the first time a visitor uses it. The default becomes [] in the next major.

Declare every parameter the page reads β€” [] only when it reads none. A page that reads ?currency= under query: [] is ONE stored document, the first visitor's, answered to every other currency. Two nets, both best-effort: x verify reads each isr route module for query.<name>, query['<name>'] and searchParams.get('<name>') and warns for a name the declaration omits (a read made through a helper in another file is not seen); and outside production, the query a page component receives warns once (isr.query.unkeyed-read) when the page reads a parameter the route did not declare. load and meta read the narrowed url, where an undeclared parameter is simply absent.

Nothing is redirected. /blog?utm_source=mail is answered with the /blog page: load and meta are given the narrowed URL, so canonical: url is /blog and a parameter outside the key cannot change the document stored under it. A declared parameter's values are still the visitor's β€” ?page=999999 is a key β€” so load answers withStatus(404, …) for one that names nothing, and the bounded store evicts the rest.

How fast a purge takes a page down

"The bust" below is the moment invalidateTags reaches its ISR step β€” after the read tiers (redis, lru, request-memo) cleared, so the re-render reads fresh rows. An action's cache.invalidates runs after its commit and is not awaited by the action's response, so the response can precede the bust by those tiers' latency.

Deployment A request after the bust gets Bound
one replica a fresh render β€” never the stored page, never a render that began before the bust (its flight is evicted; a request that had joined it renders again) the next request
several replicas, per-replica stores, bus healthy the same, on each replica once it has received the broadcast broadcast delivery, typically milliseconds; inside it a peer still answers its copy as a hit
several replicas, per-replica stores, broadcast lost a replica that never heard the bust answers its copy as a fresh hit until that copy's ttl, then renders (nothing stale past it: maxStale is 0 under 'purge') the route's ttl, at the origin and at an edge in front (s-maxage is the ttl's remainder)
several replicas over one runtime.isrStore a fresh render on every replica: the page is deleted from the one store, and a replica that began a render before the bust cannot write it back (IsrStore.tagFence) the next request, bus or no bus

Order inside one bust: read tiers (farthest first) β†’ this origin's ISR pages β†’ the broadcast to peers β†’ the cdn tier last. Each replica that receives the broadcast purges the edge again after its own delete, so a peer that answered the old page inside the broadcast window is cleared from the edge too. A render that read its rows before the bust is answered to its own request private, no-store, with no purge key, and is never stored.

Finish a rollout before relying on 'purge'. A replica still on a release before this one reads onInvalidate as nothing: it marks the page stale and answers it once more, with stale-while-revalidate=86400.

Past maxStale the copy is dropped before the re-render, so a render that fails there answers its 5xx rather than the old page β€” that is what the bound means. Ages are measured by each replica's own clock against the entry's generatedAt; over a shared store, skew between replicas moves ttl and maxStale by that skew.

A shared store must be able to refuse a write. runtime.isrStore under 'purge' needs IsrStore.tagFence β€” sample(entities) before a render reads its rows, bump(entities) on a bust, and an atomic setIfCurrent(entry, entities, sampled) (a transaction, a script, a conditional put) that refuses a write whose reading moved. The generation is per tag entity (post for post:1), so an unrelated row's bust costs one extra render and never a stale page. memoryIsrStore implements it; a store without it is refused at boot when any route purges (X_ROUTE_MODE_INVALID, naming the route). A bust also asks the store which keys it holds under the tags, by the route table, as well as the invalidation graph β€” the graph only knows the pages this process rendered. It matches on the route's tags: a post:1 bust reaches every stored page of a route tagged post.

What isr never stores.

Render Stored The visitor gets
a 5xx (withStatus(503, …) β€” "the read failed") never; never over a good copy the last good page, stale, while one exists (isr.render.unstored is logged); otherwise the 5xx itself, private, no-store. For one second (DEFAULT_ISR_FAILURE_COOLDOWN_MS; isrController({ failureCooldownMs })) requests for that page are answered the same failure, or the same stale page, instead of each rendering again
noStore(data) from load never; a stored copy of the page is dropped that render, private, no-store
a load or page that throws never the last good page, stale (isr.regenerate.failed); with none, the error page
a render invalidated while it ran never the request that started it: that render, private, no-store; any other request: a render of its own
a redirect (setRedirect()) never the redirect
a 2xx or 4xx yes β€” a 404 is a stored page it, for the ttl. A 4xx is never offered to a shared cache (no-store, no purge key): the pipeline's rule for every refusal

noStore is @ultimat3/render's, a mark on the data like withStatus and composable with it (noStore(withStatus(404, data))). isr only β€” an ssr page's cache is its route's cache key.

A page with an island is admitted to its own CSP by hash. The hydration runtime is an inline <script type="module">, and script-src admits it as one of the seven bodies HYDRATE_RUNTIME_BODIES enumerates, hashed at boot. Hashes and not a nonce because a render: 'static' page is a file on disk. The theme boot script is the second body the boot hashes, from theme.defaultMode (As of 20.2.0; an app writes neither the tag nor the hash). The speculation rules are the third (As of 2026-09, Client navigation). An app that adds its own inline script beyond those has to admit it the same way, at module scope in a file under apps/*/: configureHttp({ security: { csp: { extend: { 'script-src': [cspHashSource(body)] } } } }). The boot's own hashes are merged per directive, never replaced, so admitting a CDN source does not evict the hydration runtime's hash and lock every island out of the page.

One declaration of the vocabulary, at tier 0

As of 2026-08 the three closed sets a route is declared in β€” RENDER_MODES, OFFLINE_STRATEGIES, HYDRATE_STRATEGIES β€” are declared once, in @ultimat3/core, with each union derived from its array so the pair cannot disagree.

Import it from When
@ultimat3/core anywhere. It is tier 0, so no package is below it
@ultimat3/render you are already importing defineRoute β€” it re-exports all three, types and arrays
@ultimat3/http Β· @ultimat3/seo Β· @ultimat3/manifest Β· @ultimat3/pwa that package's own signatures take one, so it re-exports what it takes

Re-export, never restate. Six packages each kept a hand-written copy until 2026-08 β€” 14 declarations in all β€” because imports only go down tiers and copying was the move available. 'spa' was then deleted from one of them and five went on admitting it under a green project-wide typecheck; @ultimat3/pwa's copy mapped it to cache-first, the one strategy that gives an app/ route a shared cache entry. bun run scripts/render-modes.ts --json refuses a second declaration, matching on the literal set rather than the name β€” the copy that did the damage was called PwaRenderMode.

Why stream is the app default

Status, As of 2026-09-05: the design below is not what a generator emits. x g route and x new's dashboard both write render: 'ssr' on app/, deliberately: stream requires at least one <Suspense> boundary (X_ROUTE_MODE_INVALID without it), and the server JSX factory ships no hole marker yet β€” Solid's <Suspense> throws outside a Solid renderer. A page that declares stream today registers only with a boundary count it can prove, so the mode is available and the scaffold does not write it. Async data needs no boundary: await it in load.

An authed page needs fresh data and fast first paint. ssr gives freshness and a slow TTFB β€” the whole page waits for the slowest query. stream gives both halves: shell now, data as it resolves. A page whose body belongs in the browser declares an island({ src }), budgeted in real bytes; there is no client-side-only mode and no client router.

export default function Dashboard() {
  return (
    <Layout>
      <Header />                                {/* in the first flush */}
      <Suspense fallback={<StatsSkeleton />}>
        <Stats />                               {/* streamed when the query resolves */}
      </Suspense>
      <Suspense fallback={<FeedSkeleton />}>
        <Feed />                                {/* independent hole, independent flush */}
      </Suspense>
    </Layout>
  );
}

Solid's fine-grained reactivity is what makes this cheap:

Property Consequence
No VDOM streamed HTML is patched into place; nothing re-renders to reconcile
Signals, not component re-execution a resolved hole updates the exact DOM nodes bound to it
No hydration pass over the shell the static shell costs 0 hydration work β€” a <Suspense> boundary hydrates only its own island, only when its hydrate timing fires
Compile-time templates the shell's JS is markup-shaped, not a component tree to replay

In a VDOM framework a streaming shell still pays for hydrating the whole tree, which is why streaming there buys TTFB but not TBT. Here it buys both.

Independent holes each resolve their own queries; the tier-1 request memo means three holes reading the same query hit Postgres once.

hydrate timings

Value Wakes when Use
idle after first paint, on requestIdleCallback. A click made before the island mounts is captured and replayed on the same element, found by its path, as interaction does (21.0.0; the click was lost before). A keyboard or scripted click (detail: 0) is not hit-tested at (0, 0) default in app/ β€” above-the-fold interactivity
visible the island intersects the viewport below-the-fold lists, comment threads, charts
interaction first pointer/focus/key event on the island menus, modals, popovers, dropdowns
never not at all β€” server HTML is final static islands, rendered markdown, badges

Hydration is per-island, never per-page. A blown budget.js is a build failure, so islands cannot quietly accumulate.

One kind of island is hidden until it mounts, whatever the strategy (As of 2026-10): on a signed-in page with a sync node, an island that reads realtime's record store or outbox is held (data-x-hold, visibility:hidden, its box kept), booted at once, and revealed when it mounts or after 3 s. An island that only follows a channel's events, a roster or the connection is not, and with scripting off none is. The rule and the list of names: Realtime.

Budgets

Per route, and one key: budget.js, in bytes. budget.lcp is deleted β€” nothing in the build observes a paint, so it was declared and never weighed.

budget.js counts raw minified bytes on disk β€” uncompressed, As of 2026-09-05: what the browser parses and executes, never the gzip or brotli size it transfers. A 350 kB library is 350 kB against the budget however well it compresses, because the number bounds execution cost, not bandwidth. measureDocumentJs in packages/cli/src/budgets.ts is the one reader, and the X_BUDGET_EXCEEDED line says (minified, uncompressed) so the unit is never inferred.

Check Source of truth
Per-route JS bytes budget.js on the route; measured from the real bundle graph
Lighthouse SEO + a11y scores minimum thresholds in app.config.ts, defaults 100 / 95
Precache size total sw.js precache set, see PWA and offline
Regression budgets ratchet β€” the recorded baseline can tighten, never loosen silently

A blown budget is a build failure, not a warning:

x verify
  βœ“ typecheck            βœ“ import boundaries      βœ“ migration drift
  βœ“ lint                 βœ“ tests (6 types)        βœ“ contract diff
  βœ— budgets
      site/pricing   js 61kb > 40kb   (chart.js via shared/ui/button.tsx)

Failures name the cause β€” the transitive import that added the bytes β€” because "bundle too big" without a chain is not an instruction. x verify --json emits the same content machine-readably.

SEO enforcement

Concern Enforcement
meta.title missing on an indexable route build error X_SEO_META_MISSING, cause naming title and the file
meta.description missing on an indexable route the same code, X_SEO_META_MISSING, naming description β€” one code for both fields, with fix: add description to meta in <file>
Title over 60 chars, description over 160 build error X_SEO_META_TOO_LONG, with the measured length. A too-short description is not checked and no minimum is exported As of 2026-08: a 10-character description passes the gate. DESCRIPTION_MIN_LENGTH used to be exported and read by no validator, which is the shape a length bound must not ship in β€” validateMeta enforces every bound @ultimat3/seo exports, pinned by a test
Duplicate title/description across routes build error β€” duplicate meta is a ranking bug, not a style issue
og.image missing on a shareable route build error; the generated fallback OG image must be opted into explicitly
Broken internal link build error, resolved against the route table
Missing alt on an <Image> build error
Canonical URL emitted for every route from the route table; never hand-written
robots / noindex a route-level field, so "we shipped staging to Google" is impossible without editing the route

A convention that isn't a build error doesn't exist. SEO is the archetype β€” every SEO regression in history was a documented convention someone forgot.

Typed JSON-LD

meta: ({ post }) => ({ /* ... */ ld: ld.Article(post) })

Helpers, typed against schema.org shapes:

Helper For
ld.Article posts, news, docs pages
ld.Product catalog and detail pages with price + availability
ld.Organization the site's own identity, emitted once
ld.BreadcrumbList derived from the route's path segments
ld.FAQPage Q&A blocks
ld.SoftwareApplication app landing pages
ld.Event dated, located events
ld.Recipe ingredient + step content

A missing required property is a type error, not a Rich Results Test failure discovered next week. Output is a single <script type="application/ld+json">, deduped per page.

Generated from the route table

Artifact Derived from Notes
sitemap.xml all indexable routes + prerender() results one <url> per page per locale with xhtml:link alternates; lastmod and the locales a page is listed in from its prerender() entry ({ params, lastmod: row.updatedAt, locales }); an index of named sections by seo.sitemap.sections, else auto-split at 50k URLs β€” SEO
robots.txt seo.robots.disallow in app.config.ts sitemap reference included; anything but ULTIMATE_ENV=production is Disallow: /
rss.xml / atom.xml / feed.json routes tagged as feed items one declaration, three formats
llms.txt site/ route titles + descriptions machine-readable site summary for agents
404 / 500 required routes missing one is a build error

Nothing here is a plugin. Deleting a route removes it from the sitemap in the same build.

i18n routing

Feature Behavior
Locale routing /, /en/, /de/ from the locales defineCatalogs() declared β€” no per-route wiring. The prefix is stripped before matching; /<default>/x 301s to /x; an unrouted prefix is a plain 404
site/ locale the URL alone: unprefixed is always the default locale, never negotiated; no Vary: accept-language
hreflang full reciprocal set emitted per route, BCP 47 region form, including x-default; absolute against the public origin
Per-locale static output each site/ page is prerendered once per locale (index.html, en/index.html); no client-side locale swap on site/
Missing key renders ⟦key⟧ in dev, fails x verify in CI
Numbers / dates / money Intl.* with an explicit IANA timeZone and ISO currency; never a hand-rolled format
Localized metadata meta receives locale, localizedPath(locale) and alternates

See I18n, Timezones and dates, Money.

Image pipeline

<Image src={post.cover} alt={post.title} sizes="(max-width: 700px) 100vw, 700px" priority />

One <Image> shape, one capability contract, stated once β€” in docs/idea/07-rendering-seo.md β†’ Image pipeline: variants, srcset, inlined dimensions, the blur placeholder, the @ultimat3/core runtime, and the ImageTransformDriver seam AVIF/WebP variants come from. Restating it here would let the two copies drift.

A route that answers a status

As of 2026-09-07. A loader hands its data through withStatus(status, data) and the page renders as it always did β€” the route's own component, inside the app's own shell β€” with that status on the response.

import { defineRoute, withStatus } from '@ultimat3/render';

export const config = defineRoute({
  render: 'ssr',
  offline: 'runtime',
  load: async ({ params }) => {
    const host = await hostList({ id: params.host });
    return host === undefined
      ? withStatus(404, { kind: 'missing' as const, hostId: params.host })
      : { kind: 'page' as const, host };
  },
  meta: ({ data, t }) => ({ title: data.kind === 'missing' ? t('notFound') : data.host.label }),
});
what it does records the status against that object; hands the same object back, so load's return type, meta's data and the page's props.data are untouched
which statuses any 2xx, 4xx or 5xx. A 3xx is X_ROUTE_STATUS_INVALID β€” a redirect is a Location and no body, which is setRedirect() from load (below). Out of 200–599, or not whole, is refused where it is written, never as a RangeError at new Response
SEO a 4xx or 5xx is <meta name="robots" content="noindex"> by construction β€” the descriptor's meta applies it after the route's own meta ran, so a page that does not exist is never indexed however meta was written. follow and the rest stay the author's; a 200 hands meta's object back untouched
every mode ssr, stream, static-served and isr all answer it; an isr entry stores a 2xx or 4xx status beside the HTML and serves it on every hit, stale copies included; a 5xx is answered and never stored
the static export a file has no status. x build --target static writes the document whatever the loader said, and the build's measurer β€” which renders an app/ route with params: {} to weigh it β€” never fails on a loader answering 404: the status is a fact about the data, not a throw
an untouched app reads 200 everywhere it did, byte for byte β€” nothing asks unless a loader answered

A redirect is setRedirect(), from load. As of 2026-09-24. It is the same call an action's handler uses, from @ultimat3/http. On the page path, the request answers with that Location and status (default 303, and 302/307/308 when named) and no document is rendered:

import { setRedirect } from '@ultimat3/http';

load: async ({ params }) => {
  await recordOpen(params.token);            // a tracking pixel, a download link
  setRedirect('/pixel.gif', 302);
  return { params };                          // never rendered
},

With no declared cache, the redirect is private, no-store: the loader decided it for this one request, and a cached copy would answer later requests without running the loader. A route with a declared cache gets that instead. withStatus still refuses a 3xx, because a rendered document has no Location.

A refused page is withStatus(403, data). The same call answers a denial rendered in the shell β€” an @ultimat3/admin mount whose guardedPage() returned AdminPageDenied asks the same decideAll in its load and returns withStatus(403, data) (the admin README shows the load).

Not a throw, deliberately. Throwing is the other 404 and it is still there: a route the table does not have, or a loader that throws X_NOT_FOUND, gets the framework's error page β€” outside the app's shell, no sidebar, no session list. Measured in ai-maxxing on 2026-09-07, /fleet/nope rendered the right page inside the shell and answered 200, because the only way to the status was the way that lost the shell. The two answers stay distinct: the table lacks the route β†’ throw β†’ error page; the route exists and says "not found" β†’ withStatus β†’ its own page.

Error pages

A browser that hits a failure in a production process gets the framework's error page β€” the status, the code, the request id, and nothing off the throwable. Until As of 2026-08-23 a production browser got problem+json with the internal cause and the author-facing fix: in it.

status x dev production
4xx, app file present the app's file the app's file
4xx, no app file the overlay β€” cause, fix, stack the framework's page
5xx the overlay, always the app's file, else the framework's page

A 4xx in dev is the page a visitor will see, so it is the one the author sees; a 5xx is a defect, and the overlay is how dev debugs one. An agent (accept: application/json) gets problem+json in both.

a page that exists and answers 404 not this page at all β€” withStatus(404, data) in the loader renders the route's own component with the status (above); the error page is for a throw
override, one per status apps/web/site/errors/<status>.html β€” served byte for byte, exact status match only; read per request under x dev (drop a file into the running server), once per status in a container
static export 404.html is written by x build --target static, the app's file if present
copy the errors.* keys of the i18n catalog β€” an app translates or overrides by declaring them
footer links to the Ultimate repository and developerz.ai; an override replaces the whole page

Rules

  • render and offline are declared on the route or defaulted by surface. Never inferred at runtime.
  • revalidate.tags are typed handles from the tag graph β€” an unknown tag is a compile error (Caching and invalidation).
  • Routes never touch the DB directly. Data comes from a query.
  • A route holds no business logic. That is a service, behind an action.
  • prerender() must be deterministic and bounded β€” it is a build input.
  • Never hand-write a canonical URL, a sitemap entry, or sw.js.
⚠️ **GitHub.com Fallback** ⚠️