withastro / astro · Issue No. 18073
Astro v7.3.1
Node v24.12.0
System macOS (arm64)
Package Manager pnpm
Output static
Adapter @astrojs/vercel v11.0.10
Integrations none
No response
With trailingSlash: 'always', redirects declared via redirects in astro.config.mjs never fire on Vercel. Requests to the redirect source return 404.
Config used in the repro:
export default defineConfig({
output: 'static',
adapter: vercel(),
trailingSlash: 'always',
redirects: { '/old': '/new/' },
});
@astrojs/vercel generates .vercel/output/config.json where:
^/old$), even when the configured source has a trailing slash.Request flow on Vercel:
GET /old -> 308 /old/ (trailing-slash route matches first)
GET /old/ -> ^/old$ does not match
-> filesystem miss -> 404
So the configured redirect is unreachable for both /old and /old/.
Generated routes (trimmed from the committed config.json):
{ "src": "^/((?:[^/]+/)*[^/\\.]+)$", "headers": { "Location": "/$1/" }, "status": 308 },
{ "src": "^/old$", "headers": { "Location": "/new/" }, "status": 301 }
This cannot be worked around from astro.config.mjs: writing the source as '/old/' produces the identical ^/old$ regex (the trailing slash is dropped when the source is normalized to route segments), and there is no syntax for a slash-optional redirect source.
Reproducing locally: in the linked repo, pnpm install && pnpm build && pnpm repro inspects the generated .vercel/output/config.json and shows that ^/old$ cannot match the post-normalization path /old/. The generated config.json is committed in the repo.
This is the same class of bug as #16030 (redirects 404 under trailingSlash on the Cloudflare adapter), which was fixed in #16034 by making @astrojs/underscore-redirects generate patterns that respect trailingSlash. @astrojs/vercel does not use underscore-redirects — it builds Vercel routes from RouteData itself — so it never received the fix.
The adapter emits a redirect pattern that matches the normalized request path, e.g. ^/old/$ under trailingSlash: 'always' (and the equivalent for 'never'/'ignore'), matching the behavior implemented in #16034. With the config above, GET /old should 308 to /old/ and then 301 to /new/, and GET /old/ should 301 to /new/ directly.
https://github.com/mkusaka/astro-vercel-redirect-trailing-slash-repro
Relay reads this issue against the repository's contribution signals: the files it is likely to touch, how the maintainers triage work this size, and what the first contribution would exercise.
The full analysis for this issue is still being assembled. Until then, the description above and the thread on GitHub are the most reliable context.