withastro/astro · error · AstroError

RedirectWithNoLocation

RedirectWithNoLocation

Error message

A redirect must be given a location with the `Location` header.

What it means

During a static build, when a prerendered route returns a 3xx response Astro writes an HTML redirect stub and must know where to point it, so `getRedirectLocationOrThrow` reads the `Location` header and throws `RedirectWithNoLocation` (an AstroError) when it is null. The helper is invoked from the build's response handling (`core/build/generate.ts`), so this surfaces at build time for prerendered pages/endpoints that return a redirect status without a destination.

Solutions

  1. Use `Response.redirect(new URL('/target', context.url), 302)` which always sets `Location`
  2. Or add the header explicitly: `new Response(body, { status: 302, headers: { Location: '/target' } })`
  3. If forwarding an upstream redirect, propagate its `Location` header (or just return the upstream Response)
  4. Cover every 3xx branch in middleware with a header — including fallback/else paths

Example fix

// before
return new Response('Redirecting…', { status: 302 });

// after
return Response.redirect(new URL('/new-home', context.url), 302);
// or: new Response('Redirecting…', { status: 302, headers: { Location: '/new-home' } })
Defensive patterns

Strategy: type-guard

Validate before calling

// before returning a 3xx from a prerendered page/endpoint/middleware
if (response.status >= 300 && response.status < 400) {
  if (!isRedirectWithLocation(response)) {
    response = Response.redirect(new URL('/fallback', context.url), response.status);
  }
}

Type guard

// A 3xx is only a usable redirect if it carries a Location header
export function isRedirectWithLocation(res: Response): boolean {
  return res.status >= 300 && res.status < 400 && res.headers.get('location') !== null;
}

Try / catch

// Middleware safety net during prerender: never let a bare 3xx reach the build
export const onRequest = async (context, next) => {
  const res = await next();
  if (res?.status >= 300 && res?.status < 400 && !res.headers.get('location')) {
    return Response.redirect(new URL('/', context.url), res.status);
  }
  return res;
};

Prevention

When it happens

Trigger: A prerendered page or endpoint returning `new Response(body, { status: 302 })` with no `Location` header; middleware (running during prerender) returning a bare 3xx status without location; code that copies a Response's status but drops its headers before returning it.

Common situations: Hand-built redirect Responses instead of `Response.redirect()`; an upstream fetch returning 3xx and the handler forwarding only `response.status`; middleware logic branches that set status but forget the header for some routes.

Related errors


AI-assisted analysis of withastro/astro@52e6c34790 (2026-08-18). Data as JSON: /api/errors/45a03e27a9ee5589. Report an issue: GitHub.

Appendix: source

Thrown at packages/astro/src/core/redirects/validate.ts:7

import { AstroError, AstroErrorData } from '../errors/index.js';

export function getRedirectLocationOrThrow(headers: Headers): string {
	let location = headers.get('location');

	if (!location) {
		throw new AstroError({
			...AstroErrorData.RedirectWithNoLocation,
		});
	}

	return location;
}

View on GitHub (pinned to 52e6c34790)