Latest Results
refactor: unify user-triggered rewrites on Rewrites.execute; remove AstroHandler.#rewriteAndRender
Previously user-triggered rewrites (Astro.rewrite / ctx.rewrite) went
through two different code paths: AstroHandler.#rewriteAndRender for
requests handled by AstroHandler (wired via RenderContext.rewriteOverride),
and Rewrites.execute for everything else (error handlers, container).
The two paths diverged in subtle ways — notably loop detection was broken
on the AstroHandler path because each recursive rewrite built a fresh
RenderContext with counter=0.
Consolidate on Rewrites.execute for all call sites.
- RenderContext: drop the rewriteOverride field and the two
if (this.rewriteOverride) branches in createAPIContext and the
AstroGlobal builder. Both rewrite closures now call
this.#rewrites.execute(this, payload) directly. Add a public
renderContext.rewrite(payload) method that wraps #rewrites.execute for
callers (specifically i18n.finalize's fallback rewrite callback).
- AstroHandler: delete #rewriteAndRender (~70 lines) and the
renderContext.rewriteOverride = ... installation. i18n's fallback
rewrite callback now calls renderContext.rewrite(path). Drop the
pendingCookies merge block in render(state) and all the imports that
only #rewriteAndRender used (copyRequest, setOriginPathname,
AstroError, ForbiddenRewrite, getCookiesFromResponse, RenderContext,
RewritePayload).
- FetchState: drop the pendingCookies field and the AstroCookies
type-only import that fed it. No longer needed — Rewrites.execute
mutates the existing RenderContext in place so cookies don't have to
be threaded across a rewrite boundary.
Behavioral improvement: loop detection now works for user-triggered
rewrites. renderContext.counter accumulates across rewrites on the same
RenderContext, so the counter === 4 -> 508 guard fires as documented.
All tests green: build, 2340 unit, 339 i18n unit, 13 rewrite, 15
middleware, 26 actions, 12 sessions, 4 redirects, and lint:ci pass. refactor: extract Redirects handler and short-circuit redirect routes before middleware
Redirect dispatch was split across two places: AstroMiddleware
short-circuited external redirects, while PagesHandler's type switch
rendered internal redirect routes inside the middleware chain. Both
cases now go through a single Redirects handler, invoked before
middleware runs.
- New core/redirects/handler.ts: Redirects class with
handle(renderContext) that returns Response | undefined. The
routeIsRedirect() predicate lives inside the class so callers invoke
handle() unconditionally and fall through on undefined, matching the
shape of TrailingSlashHandler.handle. Delegates to the existing
renderRedirect helper for the actual response-building logic.
- AstroHandler owns a #redirects instance and calls it immediately
after createRenderContext. A truthy result short-circuits the
pipeline: no middleware, no page dispatch, no i18n post-processing.
logThisRequest + prepareResponse still run so the response is
finalized correctly.
- AstroMiddleware.handle no longer short-circuits external redirects
(the external-redirect branch plus imports for isRouteExternalRedirect
and renderRedirect are gone). Redirects are caught upstream now.
- routing/match.ts: removed the isRouteExternalRedirect export and its
redirectIsExternal import — no longer used anywhere.
Behavioral note: internal redirect routes used to go through user
middleware (user middleware could intercept and modify them). They now
short-circuit like external redirects do. Both kinds of redirect
behave consistently, and user middleware is no longer invoked for
redirect routes. PagesHandler keeps its case 'redirect' branch for
middleware-triggered rewrites that land on a redirect.
All tests green: build, 2340 unit, 26 redirect-unit, 4 redirect
integration, 15 middleware, 13 rewrite, 26 actions, and lint:ci pass. Latest Branches
-10%
-23%
-10%
© 2026 CodSpeed Technology