Skip to main content
Cactal routes requests to page files, then renders the matching page with path and query parameters. Use redirects in website_config when an old URL should move before page matching begins.

Define a page route

Every page must directly default-export definePage({ ... }) and contain one inline route string literal:
pages/post
Route parameters occupy a complete segment and begin with :. A parameter name must start with a letter and can contain letters, numbers, _, or -. Static segments can contain lowercase letters, numbers, ., _, ~, and -. Cactal does not support wildcards, catch-all parameters, optional segments, regular expressions, query strings, or hash fragments in route patterns.

Canonical route normalization

Cactal stores canonical routes in lowercase, without trailing slashes. The quality gate can rewrite a noncanonical literal such as /Blog/:Slug/ to /blog/:slug before publishing. Requests are matched case-insensitively and without a trailing slash. A request whose path is not canonical receives a 301 redirect to the canonical path, with its query string preserved. Dynamic parameter values keep their original case.
Author lowercase routes without trailing slashes so the quality gate does not need to rewrite source.

Matching priority and ambiguity

Routes with more static segments win. A fully static route beats a parameterized route:
Two routes cannot have the same normalized pattern. They also cannot differ only by parameter names, because /blog/:slug and /blog/:id match the same URLs. head/check reports every duplicate or ambiguous route it finds.

Reserved paths

Page routes and redirect sources cannot claim Cactal’s serving paths:
  • /robots.txt
  • /sitemap.xml and /sitemap-<n>.xml
  • /_framework and anything below /_framework/
/favicon.ico is not reserved. A page or authored redirect for /favicon.ico takes precedence over the website favicon setting. Use ordinary anchors for links within your website:
Cactal serves a complete server-rendered document for the first visit, refreshes, and browsers without JavaScript. After the site loads, eligible same-origin links navigate in place while Cactal retrieves the destination page’s CMS data and metadata. No router imports or special link components are required. When a destination takes long enough to load, a thin contrast-adaptive progress line appears at the top of the viewport while the current page remains available. To use a specific color instead, set --cactal-navigation-progress-color in your global CSS:
The property accepts any CSS color. Leave it unset to keep the automatic contrast-adaptive color. External links, downloads, links that open another browsing context, modified clicks, and links to a fragment on the current page keep their native browser behavior.

Read request parameters

The param reader is available in render:
pages/search
param.path(name) is type-checked against the page route and returns one string. param.query(name) returns a string, an array when the key is repeated, or undefined. The raw params and query maps contain the same values. Use the query-time parameter helper to bind request values into CMS queries. See Build pages with CMS data.

Configure redirects

Add an inline redirects array to the website_config singleton. metadata and redirects can coexist:
website_config
Each rule requires exactly three fields: Redirects run before page matching and server rendering. The first matching rule wins, so put specific rules before broader parameterized rules:
If those rules were reversed, /blog/special would redirect to /articles/special.

Redirect status codes

Browsers and search engines cache permanent redirects aggressively. Use 302 or 307 until you are certain a move is permanent.

Parameters and query strings

A target can reuse any parameter declared by its source:
A target parameter that does not exist in from fails validation. Parameter names cannot repeat within one pattern. Incoming query strings are always preserved and appended to the target. Do not include a query string or hash in from or to:

Local and external targets

Local targets begin with /. External targets must be absolute http or https URLs without credentials, query strings, or hashes:
Protocol-relative URLs, unsafe schemes, and URLs containing a username or password are rejected.

Validation and loop protection

head/check and publish reject:
  • Empty paths, whitespace, repeated slashes, trailing slashes, query strings, and hashes
  • Unsupported status codes
  • Duplicate source patterns, including duplicates that differ only by case
  • Self-redirects and local redirect loops, including loops through parameterized routes
  • Target parameters absent from the source
  • Redirects from reserved serving paths
  • Unknown, computed, duplicate, or spread properties
The redirects value must be an inline array of inline object literals. Variables, function calls, object spreads, array spreads, and computed keys are not statically extractable:

Edit redirects through the API

There is no partial redirects endpoint. Read the current head snapshot, update the complete website_config source, then upsert the singleton with PUT /v1/websiteSourceCode/files:
The file upsert replaces the complete website_config source and is not framework-validated until head/check or publish. Preserve existing metadata and redirect rules when editing it.
PATCH /v1/websiteSourceCode/metadata without a page edits only the metadata property and preserves redirects. Use it for semantic metadata changes, not redirect changes.

Publish and verify

Redirects are source-versioned. They appear on the draft host after its preview build is ready and reach production only after you publish the source version.
Confirm the status and complete Location header, including preserved query parameters. Then run head/check, publish, and repeat the request against the production hostname.

Troubleshooting

Next steps