Routes And Render Modes - developerz-ai/ultimate GitHub Wiki
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).
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 |
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.
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 |
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.
| 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.
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 |
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,metaand the page are handed that same address asurl(base path included), so the document'scanonicalis/blog/abcwhichever spelling arrived first. Astaticpage is rendered at its address too, as the export writes it;ssrandstreamkeep 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 andcanonicallink. 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
ttland 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 logsisr.key.unownedonce. The same holds for a request the router matched as one route while its address reads back as another (//en/xis the/[lang]/xroute;/en/xis the/xroute 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.
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.
"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.
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.
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.
| 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.
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.
| 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.
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.
| 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.
| 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 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.
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.
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 |
-
renderandofflineare declared on the route or defaulted by surface. Never inferred at runtime. -
revalidate.tagsare 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.