slew docs/deploy/redirects

Redirects and rewrites

Declare redirects and rewrites in a slew.json at the root of your deploy directory (or your build's output directory). The router answers them before any file lookup or server proxying, so they work the same for static sites and server runtimes.

{
  "redirects": [
    { "source": "/old-pricing", "destination": "/pricing" },
    { "source": "/moved", "destination": "/here", "status": 301 },
    { "source": "/blog/*", "destination": "https://blog.example.com/:splat", "permanent": true }
  ],
  "rewrites": [
    { "source": "/app/*", "destination": "/app/index.html" }
  ]
}

Deploy as usual — slew deploy, or a git push. The rules ship with the deployment, so a rollback also rolls back your redirects.

Redirect rules

Each rule has a source, a destination, and optionally permanent or an explicit status:

Field Meaning
source An exact path (/old-pricing), or a prefix wildcard ending in /* (/blog/*). One wildcard, only at the end. No query strings.
destination A path on the same site (/pricing) or an absolute URL (https://blog.example.com/:splat). In wildcard rules, :splat receives whatever the wildcard matched.
permanent false (default) answers 307 Temporary Redirect; true answers 308 Permanent Redirect. Both preserve the request method, so form posts survive a moved endpoint.
status When a specific code matters: 301, 302, 303, 307 or 308. Set either status or permanent, not both. The 307/308 defaults are the right choice unless a consumer demands a classic code.

Rewrites

A rewrite serves another path of the same site while the URL in the browser stays put — the classic use is a sub-app shell (/app/* → /app/index.html). Same source syntax and :splat rules as redirects; every redirect rule is checked before any rewrite.

Two boundaries, stated plainly: destinations must be paths on your own site — slew does not proxy rewrites to other origins (do that in your app) — and the root SPA fallback (/* → /index.html) is already the default for extensionless paths, so you don't need a rule for it. For server runtimes the rewritten path is what your app receives; its response is cached under that path, so two public URLs rewriting to one app path share the work.

Details worth knowing:

  • First match wins. Rules are checked in the order you declare them, up to 200 per deployment.
  • /blog/* matches /blog itself (empty :splat) and everything under /blog/.
  • Query strings are carried over/old-pricing?utm=x redirects to /pricing?utm=x. If the destination already contains a ?, the incoming query string is dropped instead of merged.
  • Exact sources match exactly: /old does not match /old/ or /old/deeper. Add a wildcard rule if you want the whole subtree.
  • A rule that would redirect a path onto itself is skipped, so a stray { "source": "/a/*", "destination": "/a/:splat" } can't loop.
  • On password- or org-protected sites, the access gate runs first — visitors see redirects only after unlocking.

Invalid rules fail the deploy with a clear message rather than deploying half a redirect map; your previous deployment stays live. Redirects and rewrites share the 200-rule budget.

Framework builds

If your framework build pipeline generates its own slew.json (an explicit manifest always wins over slew's auto-detection), add the redirects key to that file. For frameworks with a server runtime you can also keep redirects in the app itself — manifest rules are simply checked first, before the request reaches your server.