remix-run/react-router · error · Error
Invalid redirect location
Error message
Invalid redirect location
What it means
When a redirect thrown on the server crosses the RSC/error-digest boundary into client render, React Router decodes the digest, parses the redirect location, and validates its protocol against a blocklist (`javascript:`, `data:`, `blob:`, `file:`, `chrome-extension:`, etc. via `hasInvalidProtocol`). Throwing here is a deliberate security guard: silently honoring such a location would allow `javascript:` XSS through server-controlled redirects.
Source
Thrown at packages/react-router/lib/hooks.tsx:1177
error &&
"digest" in error &&
typeof error.digest === "string"
) {
let redirect = decodeRedirectErrorDigest(error.digest);
if (redirect) {
let existingRedirect = errorRedirectHandledMap.get(error);
if (existingRedirect) throw existingRedirect;
let parsed = parseToInfo(redirect.location, basename);
let target = parsed.absoluteURL || parsed.to;
validateNavigationTarget(
redirect.location,
target,
getNavigatorCurrentUrl(navigator),
"allow-explicit",
);
if (hasInvalidProtocol(target)) {
throw new Error("Invalid redirect location");
}
if (isBrowser && !errorRedirectHandledMap.get(error)) {
if (parsed.isExternal || redirect.reloadDocument) {
window.location.href = target;
} else {
const redirectPromise: Promise<void> = Promise.resolve().then(() =>
window.__reactRouterDataRouter!.navigate(parsed.to, {
replace: redirect.replace,
}),
);
errorRedirectHandledMap.set(error, redirectPromise);
throw redirectPromise;
}
}
return <meta httpEquiv="refresh" content={`0;url=${target}`} />;
}View on GitHub (pinned to 7aea711dd1)
Solutions
- Validate redirect targets against an allowlist before calling `redirect()`: only allow relative paths or `http:`/`https:` URLs.
- Prefer relative locations (`redirect(url.pathname + url.search)`) after parsing with `new URL(value, request.url)`.
- Reject or sanitize user-provided redirect inputs at the trust boundary (loader args, form fields).
- Audit any DB-stored or config-driven redirect destinations for unsafe schemes.
Example fix
// before
export async function loader({ request }: LoaderFunctionArgs) {
const next = new URL(request.url).searchParams.get("next") ?? "/";
return redirect(next);
}
// after
export async function loader({ request }: LoaderFunctionArgs) {
const raw = new URL(request.url).searchParams.get("next") ?? "/";
const url = new URL(raw, request.url);
if (url.origin !== new URL(request.url).origin) throw redirect("/");
return redirect(url.pathname + url.search);
} Defensive patterns
Strategy: validation
Validate before calling
function safeRedirectTarget(raw: string, request: Request): string | null {
try {
const url = new URL(raw, request.url);
return url.protocol === "http:" || url.protocol === "https:"
? url.pathname + url.search
: null;
} catch {
return null;
}
} Type guard
const isSafeRedirect = (raw: string): boolean => {
try {
const p = new URL(raw, "http://x").protocol;
return p === "http:" || p === "https:";
} catch {
return false;
}
}; Prevention
- Allowlist protocols on every redirect target derived from user input.
- Prefer relative redirects (pathname + search) over absolute URLs.
- Add a test that asserts redirects reject javascript:/data: targets.
When it happens
Trigger: `redirect("javascript:alert(1)")` or a redirect to a `data:` URL thrown from a loader/action/middleware; a redirect location built from user input (query param `?next=`) that contains an unsafe scheme; an attacker-supplied URL stored in a DB and used as a redirect target.
Common situations: Open-redirect style flows (`redirect(searchParams.get("next"))`) where the value is attacker-controlled; importing legacy URLs that embed `javascript:` links; penetration tests flagging redirect handling.
Related errors
- Invalid redirect location
- Invalid redirect location
- ⚠️ Source maps are enabled in production This makes your se
- `origin` header is not a valid URL. Aborting the action.
- The `request.url` origin does not match `origin` header from
AI-assisted analysis of remix-run/react-router@7aea711dd1 (2026-08-28).
Data as JSON: /api/errors/92dd66ab913eca72.
Report an issue: GitHub.