withastro/astro · error · AstroError
InvalidRedirectDestination
InvalidRedirectDestination
Error message
The redirect from "${from}" to "${to}" is invalid. The destination "${to}" does not match any existing route in your project. What it means
When a `redirects` source contains dynamic params (e.g. `/old/[slug]`), the static build must substitute those params into the destination to emit one redirect file per value — which requires the destination to resolve to a real route. If the destination is not a URL, matches nothing in the route map, and the config prerenders by default, Astro throws `InvalidRedirectDestination` at manifest creation instead of failing later with a misleading error.
Solutions
- Fix the destination to an existing route (check `src/pages` for the exact pattern)
- Carry the dynamic segment over: `'/old/[slug]': '/posts/[slug]'`
- Use a full `https://…` URL for external targets (URLs skip route matching)
- Add a CI check that every non-URL redirect destination matches a page in `src/pages`
Example fix
// before
redirects: { '/old/[slug]': '/new-posts' } // no /new-posts route exists
// after
redirects: { '/old/[slug]': '/posts/[slug]' } // existing dynamic route Defensive patterns
Strategy: validation
Validate before calling
// Pre-build check: every non-URL redirect destination must match a pages route
import { getRoutes } from './route-helpers.js'; // builds expected paths from src/pages
const known = await getRoutes(); // e.g. Set like '/posts/[slug]', '/about'
for (const [from, to] of Object.entries(redirects)) {
const dest = typeof to === 'string' ? to : to.destination;
if (URL.canParse(dest)) continue; // external, fine
if (from.includes('[') && ![...known].some((r) => routeMatches(r, dest))) {
throw new Error(`redirect ${from} → ${dest}: destination route not found`);
}
} Prevention
- After renaming/removing routes, grep the redirects map for the old paths
- Prefer destinations that carry the same dynamic segments as the source
- Add a config-validation script to CI that cross-checks redirects against src/pages
When it happens
Trigger: `redirects: { '/old/[slug]': '/new-posts' }` where no `/new-posts` route exists (typo or never created); renaming destination routes without updating the redirects map; destinations with leading-path typos like `/post/[slug]` vs `/posts/[slug]`.
Common situations: Maintaining a legacy redirect table as the site evolves; content migrations that move routes after redirects were written; large configs where a stale entry goes unnoticed until build.
Related errors
- Error generating redirects
- MissingIndexForInternationalizationError
- Parameter name must match /^[a-zA-Z0-9_$]+$/
- PrerenderDynamicEndpointPathCollide
- The Sitemap integration requires the `site` astro.config…
AI-assisted analysis of withastro/astro@e294953aa8 (2026-08-18).
Data as JSON: /api/errors/f018f9d0ab18275d.
Report an issue: GitHub.
Appendix: source
Thrown at packages/astro/src/core/routing/create-manifest.ts:559
if (URL.canParse(destination) && !/^https?:\/\//.test(destination)) {
throw new AstroError({
...UnsupportedExternalRedirect,
message: UnsupportedExternalRedirect.message(from, destination),
});
}
const redirectRoute = routeMap.get(destination);
// If the source has dynamic params and the redirect will be prerendered,
// we need a valid redirectRoute to map them. Without it, the build will fail
// later with a misleading error. Catch this early and provide a clear error message.
if (
params.length > 0 &&
!redirectRoute &&
!URL.canParse(destination) &&
getPrerenderDefault(config)
) {
throw new AstroError({
...InvalidRedirectDestination,
message: InvalidRedirectDestination.message(from, destination),
});
}
// If the source has dynamic params and the destination route exists but has
// fewer params, the build will fail later with a misleading error (e.g.,
// "getStaticPaths required" pointing at the wrong file). Catch this early.
if (
params.length > 0 &&
redirectRoute &&
!URL.canParse(destination) &&
getPrerenderDefault(config)
) {
const destParams = redirectRoute.params ?? [];
const missingParams = params.filter((p) => !destParams.includes(p));
if (missingParams.length > 0) {
throw new AstroError({View on GitHub (pinned to e294953aa8)