withastro/astro · error · AstroError

ForbiddenRewrite

ForbiddenRewrite

Error message

You tried to rewrite the on-demand route '${from}' with the static route '${to}', when using the 'server' output. 

The static route '${to}' is rendered by the component
'${component}', which is marked as prerendered. This is a forbidden operation because during the build, the component '${component}' is compiled to an
HTML file, which can't be retrieved at runtime by Astro.

What it means

With `output: 'server'`, an on-demand (non-prerendered) route may not `rewrite()` to a prerendered route: during build the target component is compiled to a static HTML file and is absent from the runtime server manifest, so nothing can render it on demand. The rewrite handler therefore throws `ForbiddenRewrite` before swapping state, with an explicit exception for i18n fallback routes (`fallbackRoutes` non-empty).

Solutions

  1. Make the rewrite target on-demand: remove `export const prerender = true` from that page (it must be renderable at runtime)
  2. Use `redirect()` instead of `rewrite()` — a redirect lets the client fetch the static HTML normally
  3. Move the shared content into a component or API both routes consume, instead of rewriting between output modes

Example fix

// before — src/middleware.ts (output: 'server', /login is prerendered)
export const onRequest = (context, next) => {
  if (context.url.pathname.startsWith('/admin')) return rewrite('/login');
  return next();
};

// after
import { redirect } from 'astro:middleware';
export const onRequest = (context, next) => {
  if (context.url.pathname.startsWith('/admin')) return redirect('/login');
  return next();
};
// or remove `export const prerender = true` from src/pages/login.astro
Defensive patterns

Strategy: fallback

Validate before calling

// Keep an explicit list of prerendered paths and guard rewrites against it
const PRERENDERED = new Set(['/about', '/pricing']); // keep in sync with prerender flags
export const onRequest = async (context, next) => {
  if (needsRewrite(context) && !PRERENDERED.has(target)) {
    return rewrite(target);
  }
  return next();
};

Try / catch

// src/middleware.ts — degrade rewrites-to-static into redirects
export const onRequest = async (context, next) => {
  try {
    return await next();
  } catch (err) {
    if (err instanceof Error && /forbidden operation/i.test(err.message)) {
      return context.redirect(context.url.pathname); // or a safe on-demand route
    }
    throw err;
  }
};

Prevention

When it happens

Trigger: Middleware on an SSR route returning `rewrite('/about')` where `src/pages/about.astro` has `export const prerender = true`; an on-demand page rewriting to any prerendered page under server output; hybrid apps where marketing pages are static but a global middleware rewrites into them.

Common situations: Adding auth middleware that rewrites to a prerendered `/login`; incrementally adopting islands/hybrid prerender while keeping old rewrites; migrating from static to server output without auditing rewrite targets.

Understand the failure class

Related errors


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

Appendix: source

Thrown at packages/astro/src/core/rewrites/handler.ts:53

export function applyRewriteToState(
	state: FetchState,
	payload: RewritePayload,
	{ routeData, componentInstance, newUrl, pathname }: TryRewriteResult,
	{ mergeCookies = false }: { mergeCookies?: boolean } = {},
): void {
	const oldPathname = state.pathname;

	// Disallow SSR→prerender rewrites: the prerendered route becomes a
	// static HTML file during build and isn't available in the server
	// manifest. Allow i18n fallback routes as an exception.
	const isI18nFallback = routeData.fallbackRoutes && routeData.fallbackRoutes.length > 0;
	if (
		state.manifest.serverLike &&
		!state.routeData!.prerender &&
		routeData.prerender &&
		!isI18nFallback
	) {
		throw new AstroError({
			...ForbiddenRewrite,
			message: ForbiddenRewrite.message(state.pathname, pathname, routeData.component),
			hint: ForbiddenRewrite.hint(routeData.component),
		});
	}

	state.routeData = routeData;
	state.componentInstance = componentInstance;
	if (payload instanceof Request) {
		state.request = payload;
	} else {
		state.request = copyRequest(
			newUrl,
			state.request,
			routeData.prerender,
			state.logger,
			state.routeData!.route,
		);

View on GitHub (pinned to e294953aa8)