Skip to main content

Routing

Path resolution is RouteIndex-driven. Every published, routable entry has a row in RouteIndex; delivery looks up the row and dispatches to the right entry / redirect / 404.

Resolution flow

  1. Hostname → site. The delivery handler matches the request's Host header against Site.hostnames[] and resolves the project + site.
  2. Path + locale → RouteIndex. Looks up the unique row keyed by (projectId, environmentId, siteId, locale, path).
  3. If found:
    • isRedirect=false → return the entry. Frontend renders.
    • isRedirect=true → return { kind: "redirect", target, status }. Frontend emits HTTP redirect.
  4. No match → 404. The delivery API resolves exact RouteIndex matches only. Regex redirect matching is not wired into the delivery path, so a path that only matches a regex redirect resolves to 404 at the delivery API.

RouteIndex shape

{
projectId: string;
environmentId: string;
siteId: string;
locale: string;
path: string; // normalized: "/about/team"
entryId: string | null; // null on regex redirects
contentTypeApiName: string | null;
isRedirect: boolean;
isRegex: boolean;
redirectTarget: string | null;
redirectStatus: number | null; // 301, 302, 307, 308
sortOrder: number; // for regex redirects only
status: "published" | "draft";
}

Path computation

For a published, routable entry:

  • Has a tree node + slug not overridden → TreeNode.path (materialized).
  • Tree node + slug overridden → tree path with the last segment swapped for the entry's slug.
  • No tree node, has slug → /{slug}.
  • No slug → entry isn't addressable, no RouteIndex row.

Redirects

Two flavors:

Plain (exact)

source is a literal path. RouteIndex.path stores the normalized form; lookup is a single keyed query.

Regex (V2)

source is a regex pattern. Stored verbatim with isRegex=true, ordered by sortOrder, with capture groups ($1, $2) substituting into the target. Note: regex redirect matching is not wired into the delivery resolution path — a path that only matches a regex redirect resolves to 404 at the delivery API.

source: ^/blog/(\d{4})/(.+)$
target: /articles/$2
status: 301

Auto-redirect on slug change

When a published entry's slug changes, Krios automatically creates a 301 redirect from the old path to the new one. Idempotent — re-publishing into the same path is a no-op. Cycling A → B → A refreshes existing redirect entries instead of accumulating duplicates.

Resolving a route

GET /api/delivery/projects/{slug}/sites/{siteSlug}/routes?path=/about/team

Response:

{ "data": { "kind": "entry", "entry": { ... } } }
{ "data": { "kind": "redirect", "target": "/articles/team", "status": 301 } }

A path with no match returns HTTP 404:

{ "error": "route_not_found", "message": "No route matches the requested path" }

The SDK's KriosClient.resolveRoute(siteSlug, path) wraps this:

const result = await krios.resolveRoute("main", "/about/team");
switch (result.kind) {
case "entry": return render(result.entry);
case "redirect": return Response.redirect(result.target, result.status);
case "notFound": return notFound();
}

Sitemaps

GET /api/delivery/projects/{slug}/sites/{siteSlug}/sitemap?locale=en-US&format=xml

XML sitemap with <xhtml:link rel="alternate" hreflang="..."> for every supported locale. JSON format available for programmatic crawlers. Capped at 50,000 URLs per response (sitemap protocol limit); sitemap-index splitting for larger sites is on the V3.x roadmap.