Aller au contenu
Bastide Connexion Créer un compte
Press / to search

Routing: redirects, rewrites and headers

FrançaisMarkdownv0.3.0

Bastide's edge can redirect a request, rewrite it to another path or another origin, and set response headers, before your application even receives it. This page presents the two places where these rules are declared, the simulator that checks them before publication, and how to go back to a previous version.

Two places, two uses

Project routing rulesDeployment routes
Where they liveIn the database, per projectIn the repository's glowsoft.json file
How they changeThrough the API or the command line, without redeployingWith each deployment, along with the code
What they doRedirects, internal rewrites and rewrites to an external originRedirects, response headers, internal rewrites
SyntaxSimple * and :name patternsThat of vercel.json
VolumeUp to 50,000 rules2,048 rules per block
History90 days of versions, one-step rollbackThat of deployments: see Restore

Choose project rules for bulk redirects and for those that change without shipping code (a campaign, an address migration). Choose glowsoft.json for what follows the code: headers, rewrites for a single-page application, redirects that a release introduces.

Evaluation order

For each request, the edge applies, in this order: the project's rate limit, preview protection, the project's routing rules, then the deployment routes, then the cache, and finally the application. Project rules therefore win over the repository's, and a project rewrite is read again by the deployment routes. A redirected request reads nothing from the cache and does not wake a sleeping application.

Project routing rules

Project rules are published and read through the API and the command line. There is no console screen for them yet, and they are not open to agents: the MCP catalogue has no tool for them. A service account cannot use them either.

A rule

FieldMeaning
sourceThe path pattern, starting with /. * matches any run of characters, / included; :name matches one segment. No regular expressions.
destinationA path on the site, or an https:// URL to a host on the allow list (rewrite only). :name and * reuse the captures, in order.
typeredirect or rewrite.
statusRedirect only: 301, 302, 307 or 308. Defaults to 308.
conditionsUp to 8 conditions on a request header: equals, prefix, exists or absent.
timeoutMsExternal rewrite: response timeout, 5,000 ms by default, 10,000 ms at most.
maxBytesExternal rewrite: response size, 5 MB by default, 10 MB at most.

The order of the list is the precedence: the first rule whose pattern and conditions match the request answers. Write the specific rule before the general rule that covers it; the other way round, the specific rule would never be reached, and the simulator refuses it.

External origins

A rewrite to https://host/… is only accepted if the host is in allowedHosts, the project's allow list. It only takes public domain names, in lowercase, without port or path: no IP address, no localhost, no internal suffix such as .local or .internal. The edge checks the host, the timeout and the size again at run time.

Publishing

routingrules.write replaces the project's entire list: always send the complete list, in order. It becomes a numbered version, picked up by each node on its next pass, without redeploying. A list identical to the active version writes nothing.

{
  "projectId": "prj_01JQ8ZK4M7NP2RSTVWXYZ34567",
  "rules": [
    { "source": "/old/:page", "destination": "/new/:page", "type": "redirect", "status": 301 },
    { "source": "/api/*", "destination": "https://api.example.com/v1/*", "type": "rewrite", "timeoutMs": 3000 }
  ],
  "allowedHosts": ["api.example.com"],
  "urls": [{ "path": "/old/pricing" }, { "path": "/api/products" }],
  "ifVersion": 3,
  "idempotencyKey": "routing-2026-10-26"
}

ifVersion carries the active version you read (0 if the project has none). If someone published in the meantime, the call is refused with 409 rather than overwriting their version.

The simulator

Before writing anything, publication replays the list. A refused list writes nothing: the response is a 422 detailing each conflict. With dryRun: true, nothing is ever written and the report is returned, even if it refuses.

Conflicts (conflicts) carry a nature:

NatureMeaning
loopA chain of rules comes back on itself, or exceeds 8 hops.
shadowedA rule is never reached: a more general rule precedes it.
collisionTwo wildcard patterns overlap and the order does not settle them.
duplicateTwo identical rules.
invalidA refused form, or a rewrite to a host outside the list.
incompleteThe simulation hit its comparison budget without reaching a conclusion.

For each URL in the set given in urls (1,000 at most, with optional headers), results gives the outcome: redirect, rewrite, external, none (no rule) or loop, with the destination, the status and the rules traversed. Build this set from the addresses that matter to you: it acts as a regression test on every publication.

Versions and rollback

Each publication creates an immutable version: its number, date, author, rule count and fingerprint. The history keeps 90 days of versions.

routingrules.read returns the rules of the active version, or of a given version, page by page (200 by default, 1,000 at most), with the allow list and the history.

routingrules.rollback restores a version from the history. It copies it into a new version, after replaying the simulator: the history stays linear, and the rollback can itself be undone by restoring the version before it.

bastide routingrules read --project-id prj_01JQ8ZK4M7NP2RSTVWXYZ34567
bastide routingrules write --project-id prj_01JQ8ZK4M7NP2RSTVWXYZ34567 --rules "$(cat rules.json)" --allowed-hosts '["api.example.com"]' --dry-run
bastide routingrules rollback --project-id prj_01JQ8ZK4M7NP2RSTVWXYZ34567 --version 3

Through the API: GET and PUT /v1/projects/{projectId}/routing-rules, and POST /v1/projects/{projectId}/routing-rules/rollback, on https://api.bastide.app. Reading requires the Reader rank; publishing and rolling back, the Maintainer rank.

Deployment routes

The glowsoft.json file, at the root of the repository, accepts three blocks whose shape and meaning are those of vercel.json: redirects, headers and rewrites. They are compiled at build time and frozen with the deployment: changing them requires a new deployment, and restoring a previous version also restores its routes. A rule the edge could not apply fails the build, with a message that locates the rule.

{
  "redirects": [
    { "source": "/blog/:slug", "destination": "/articles/:slug", "permanent": true }
  ],
  "headers": [
    {
      "source": "/:path*",
      "headers": [{ "key": "Strict-Transport-Security", "value": "max-age=31536000" }]
    }
  ],
  "rewrites": [
    { "source": "/:path*", "destination": "/index.html" }
  ]
}
  • Redirects: the first that matches the request answers. Without permanent or statusCode, the status is 308; "permanent": false gives 307. The has and missing conditions apply to a header, a cookie, a query parameter or the host. The env key is refused: the edge does not hold the project's variables.
  • Headers: every rule that matches the request applies, in order. A header the application sets itself wins.
  • Rewrites: the requested path is served first; a rewrite is only tried if the application answers 404 there, three at most per request. A destination on another origin is refused: use a project rule for that.

As in vercel.json, a pattern is case-sensitive, and /:path* does not cover the root /.

Default security headers

The edge sets four security headers, frozen with each deployment:

HeaderValue
X-Content-Type-Optionsnosniff
Referrer-Policystrict-origin-when-cross-origin
X-Frame-OptionsSAMEORIGIN
Permissions-Policybrowsing-topics=()

A headers block overrides them, and an empty value removes one. Strict-Transport-Security and a content security policy are not set by default: declare them if you want them.

Limits

LimitProject rulesDeployment routes
Number of rules50,0002,048 per block
Weight4 MB of JSON per project; 16 MB request body256 KB of compiled rules
Wildcards per pattern4—
Length of a pattern or destination2,048 characters4,096 characters
Conditions per rule816 has and 16 missing
Allowed external hosts50none
History90 daysthat of deployments

These limits are the same on every tier.

Common errors

Symptom or codeCauseRemedy
422 on publicationThe simulator found a conflict.Read conflicts, fix the order or the rule, replay with dryRun.
409 conflictifVersion no longer matches the active version.Read again with routingrules.read and redo your changes on the current version.
invalid on an external rewriteThe host is not in allowedHosts, or is not a public domain.Add the exact host, in lowercase, without a port.
shadowedA general rule precedes a specific one.Move the specific rule first.
508 served to the visitorA chain of rules exceeds 8 hops at run time.Simplify the chain; the simulator reports it as loop.
The build fails on glowsoft.jsonA route uses a form the edge does not apply.Read the message, which names the block and the rule's position.
A repository rewrite does not applyThe application answers something other than 404 on the requested path.This is intended: existing content wins.

See also

Report a mistake on this page