Conversation
…handler.ts Move the request handling logic (trailing slash redirects, route matching, rendering, error handling, session persistence, reroutable status codes) from BaseApp#render() into a new AstroHandler class. BaseApp#render() now delegates to AstroHandler#handle(). This is a pure code extraction with no behavioral changes -- the same code runs in the same order. The goal is to create a seam for future incremental refactoring of the request pipeline. Also changes #prepareResponse to a non-private method so both BaseApp (renderError) and AstroHandler can call it.
🦋 Changeset detectedLatest commit: 63f5566 The changes in this PR will be included in the next version bump. Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
…roHandler Add an intermediate FetchHandler layer so future work can compose additional handlers without changing BaseApp. DefaultFetchHandler owns an AstroHandler instance and exposes a standard fetch(request) signature. - New FetchHandler type in core/fetch/types.ts - New DefaultFetchHandler in core/fetch/default-handler.ts - New render-options module in core/app/render-options.ts with the symbol and helpers for attaching ResolvedRenderOptions to a Request - AstroHandler#handle() now takes only a request and reads options from the request via the symbol, keeping FetchHandler signatures clean - BaseApp#render() attaches resolved options to the request and delegates to DefaultFetchHandler#fetch() Pure refactor with no behavioral changes. All tests pass.
- Annotate DefaultFetchHandler#fetch with the FetchHandler type so types.ts is no longer unused (fixes knip 'unused files'). - Drop the export on renderOptionsSymbol since only same-file helpers use it (fixes knip 'unused exports').
Decouple renderError and prepareResponse from BaseApp so the request pipeline no longer depends on app-level methods for these concerns. - prepareResponse moves to core/app/prepare-response.ts as a pure helper. AstroHandler and the error handlers call it directly instead of going through BaseApp. - Introduce an ErrorHandler interface in core/errors/handler.ts. - DefaultErrorHandler (core/errors/default-handler.ts) holds the 404/500 route rendering, prerendered error page fetch, and mergeResponses logic that previously lived on BaseApp. - DevErrorHandler (core/errors/dev-handler.ts) is shared by the Vite dev server and the non-runnable dev pipeline, parameterized by a shouldInjectCspMetaTags flag (the only real difference between the two overrides). - BuildErrorHandler (core/errors/build-handler.ts) throws on 500 and delegates other errors to DefaultErrorHandler with prerenderedErrorPageFetch cleared. - BaseApp exposes a protected createErrorHandler() factory; subclasses override this instead of overriding renderError. BaseApp#renderError() now just forwards to the configured handler. Pure refactor with no behavioral changes. All tests pass, lint:ci passes.
Move the trailing-slash normalization + redirect logic into its own class so it can be composed independently of the full AstroHandler pipeline. TrailingSlashHandler#handle(request) returns either a redirect Response or undefined (meaning 'no redirect needed, continue'). AstroHandler now owns a TrailingSlashHandler and short-circuits on its response. No behavioral changes; all tests and lint pass.
Introduce an AstroMiddleware class that owns the middleware orchestration previously embedded in RenderContext.render(). AstroHandler now owns a single AstroMiddleware instance and invokes it directly instead of going through RenderContext.render() on the happy path. - New AstroMiddleware class in core/middleware/astro-middleware.ts takes a Pipeline and composes sequence(...internalMiddleware, userMiddleware) at render time. Its handle(renderContext, componentInstance, slots?) method encapsulates the full render orchestration: build props, create apiContext + actionApiContext, loop-detection counter, external redirect short-circuit, skipMiddleware branch, callMiddleware call, and response finalization (strip ROUTE_TYPE_HEADER, attach cookies). - RenderContext no longer stores a middleware field. The old 'lastNext' closure is now the public RenderContext.renderRoute() method, which mutates context state on rewrites (routeData, pathname, url, etc.) and dispatches to the appropriate render function by route type. - RenderContext.render() remains as a thin entry point used by the error handlers and the container: it builds a fresh AstroMiddleware and delegates. - AstroHandler owns #astroMiddleware = new AstroMiddleware(app.pipeline) and calls astroMiddleware.handle() at the three former renderContext.render() call sites (cache onRequest, CDN cache path, no-cache path). Pure refactor with no behavioral changes. All tests pass, lint:ci passes.
…ler + BaseApp Centralize per-request state (routeData + pathname) into a new FetchState class. This replaces a tangle of ad-hoc locals in AstroHandler.handle(), validates the matched route in one place, and removes the need for getPathnameFromRequest to live on BaseApp. - New FetchState class in core/app/fetch-state.ts holds the request, the matched routeData, and the resolved pathname. Its constructor computes the raw pathname (base strip + decodeURI with fallback); validateRouteData() runs the full routing flow (adapter-provided, dev/prod match, 404 fallback, .html normalization) and returns a boolean indicating success. - Several kinds of validation previously inlined in AstroHandler.handle (adapter routeData logging, locals type check, routeData resolution, pathname computation + normalization) now live either on BaseApp.render (input contract checks) or on FetchState (per-request resolution). - BaseApp.getPathnameFromRequest removed. The only external caller — the Cloudflare integration's dev-match fallback path — now inlines the same 8-line computation. - RenderErrorOptions gains an optional pathname; the default + dev error handlers use it when provided and otherwise fall back to a short-lived FetchState for the raw pathname. Recursive retries (middleware-skip path) thread pathname through so it is preserved. Pure refactor. Build, 2340 unit tests, and lint:ci all pass.
… of rendering Introduce a single ActionHandler that handles both Astro Action modes in one place, upstream of RenderContext.render(): - RPC (POST to /_actions/<name>): runs the action and returns the serialized result as the response, short-circuiting the pipeline. The /_actions/[...path] RouteData still exists in the manifest but the endpoint is no longer reached because the handler intercepts earlier. - Form (POST to a page URL with ?_action=<name>): runs the action, stores the result in locals._actionPayload, and returns undefined so the pipeline continues to render the page. A subsequent call to getActionContext during page rendering sees the stored payload and skips re-running. Wiring: - AstroHandler owns a single ActionHandler instance and calls actionHandler.handle(renderContext) immediately after createRenderContext. An RPC response is logged, finalized via prepareResponse, and returned. Otherwise rendering continues. - The form-action auto-execution block previously inlined in RenderContext.renderRoute is removed. The getActionContext import in render-context.ts is no longer needed. Tests: - One unit test in render-context.test.ts asserted that calling renderContext.render() directly auto-executes form actions. That coupling was exactly what we moved; the end-to-end behavior (POST form -> action runs -> page renders) is preserved through AstroHandler. The test is skipped with a TODO; we'll revisit by adding ActionHandler-level coverage later. Build, 2339 unit tests (1 newly skipped), and lint:ci all pass.
… finalization run around it The previous commit wired ActionHandler ahead of AstroMiddleware in AstroHandler.handle, which bypassed user middleware and the response finalization step (attachCookiesToResponse). That broke: - actions tests where middleware intercepts action requests (custom errors, response fallback, RPC middleware handling). - sessions tests that expected session cookies to be attached to RPC responses. Move the ActionHandler call back into RenderContext.renderRoute (same position the old form-action block lived) so it runs from inside the middleware chain's 'next' callback. Middleware sees action requests first, and the response flows through AstroMiddleware.#finalize which strips ROUTE_TYPE_HEADER and attaches cookies. The unified single-handler-for-both-modes design is preserved; only the placement changed. - ActionHandler.handle now takes APIContext (from renderRoute's ctx). - AstroHandler.handle no longer references ActionHandler. - The form-action unit test in render-context.test.ts is unskipped (end-to-end behavior through renderContext.render is preserved).
… AstroMiddleware Introduce a new PagesHandler class that owns dispatch of the matched route (endpoint / redirect / page / fallback), in-flight rewrites, and the ActionHandler invocation. Previously this logic lived inline in RenderContext.renderRoute and was then duplicated onto AstroHandler as part of the callback-passing refactor. Both callers now delegate to a single PagesHandler implementation. - core/pages/handler.ts: new PagesHandler(pipeline) class. Its handle() method contains the full body of the old renderRoute: rewrite handling, ActionHandler dispatch, the switch over routeData.type, and the cookie merge-back. Holds its own #actionHandler and the pipeline. - AstroMiddleware.handle() accepts an optional RenderRouteCallback. When provided (the AstroHandler happy path), it's used as the 'next' callback at the bottom of the middleware chain. When omitted (error handlers, container), it falls back to renderContext.renderRoute. - RenderContext.render() accepts and forwards the optional callback to AstroMiddleware. RenderContext owns a #pagesHandler and renderRoute() is a thin delegate. - AstroHandler owns a single #pagesHandler and passes its handle method as the callback to AstroMiddleware. AstroHandler no longer has its own renderRoute copy. - RenderContext.url and RenderContext.createNormalizedUrl are now public (previously protected / private static) so PagesHandler can update url on rewrites. Pure refactor. Build, 2340 unit tests, 26 actions integration tests, and lint:ci all pass.
…rs instead of RenderContext.render
Three related cleanups that continue decoupling rendering logic from
RenderContext:
1. Error handlers (DefaultErrorHandler, DevErrorHandler) now own their
own AstroMiddleware + PagesHandler instances, built from the app's
pipeline in the constructor. They call
astroMiddleware.handle(renderContext, mod, {}, pagesHandler.handle)
directly instead of going through renderContext.render(mod). This
removes them as consumers of RenderContext's internal fallback path.
2. The container API (experimental_AstroContainer) does the same: holds
its own AstroMiddleware + PagesHandler and dispatches through them
instead of renderContext.render(componentInstance, slots).
3. User-triggered rewrites (Astro.rewrite / ctx.rewrite) now recurse
through AstroHandler instead of RenderContext.#executeRewrite.
AstroHandler.handle is split into handle(request) (trailing slash,
FetchState, routeData validation) and render(state) (the inner
pipeline). A new #rewriteAndRender(state, payload) mutates the
FetchState in place and calls render(state) again, producing a
fresh RenderContext while carrying locals / renderOptions /
timeStart / request / routeData / pathname across the rewrite.
FetchState gains a mutable request, a renderOptions field (resolved
from the render-options symbol up front), and a timeStart field.
RenderContext exposes a rewriteOverride hook that
createAPIContext and the AstroGlobal check before falling back to
the legacy #executeRewrite (still used by error handlers and the
container).
RenderContext.create now only calls setOriginPathname when the
request doesn't already have one recorded, so the origin pathname
preserved by #rewriteAndRender survives the new RenderContext.
All tests green: full build, 2340 unit, 26 actions, 12 sessions,
10 container, 13 rewrite, and lint:ci pass.
… rewrite With rewrites now producing a fresh RenderContext per call, cookies set before and during a rewrite were being lost because each RenderContext owns its own AstroCookies bag and AstroMiddleware.#finalize overwrites the response's cookies symbol instead of merging. In the old flow, inner and outer renders shared a single RenderContext (and thus a single cookie bag), so overwriting was benign. Thread cookies across the rewrite boundary in both directions: - FetchState.pendingCookies carries cookies that were set on the outer render forward into the inner render (merged into the new RenderContext's cookies before its middleware runs). - After the inner render returns, read cookies off the response and merge them into the outer RenderContext's cookies so that when the outer AstroMiddleware.#finalize attaches cookies to the response, it includes what the inner render set. Fixes: 'should preserve cookies set in sequence' in middleware.test.js (middleware-sequence-rewrite fixture).
…ck required RenderContext is no longer responsible for rendering. All rendering now flows through AstroMiddleware + PagesHandler explicitly. Production: - Extract Rewrites class to core/rewrites/handler.ts holding the body that was previously RenderContext.#executeRewrite. Rewrites.execute now drives the recursive render by constructing its own AstroMiddleware + PagesHandler instead of calling back into RenderContext.render. - AstroMiddleware.handle's renderRouteCallback is now required; the fallback to RenderContext.renderRoute is gone. - RenderContext.render and RenderContext.renderRoute are removed. RenderContext no longer imports AstroMiddleware or PagesHandler. - The cookies field on RenderContext is now public so Rewrites can rebind it after a rewrite. Tests: - New renderThroughMiddleware(renderContext, componentInstance, slots?) helper in test/units/test-utils.ts that wires up the middleware + pages handler stack and returns the response. - Updated all 7 unit test call sites (head-injection-app, render-context, head, csp/rendering) and the createMockPrerenderer helper in build/test-helpers.ts to use the new helper instead of RenderContext.render. Container: - Minor cleanup: slots default now propagates correctly. All tests green: build, 2340 unit, 13 rewrite, 15 middleware, 26 actions, 12 sessions, 10 container, and lint:ci pass.
…class i18n post-processing (routing strategy dispatch + fallback rewrite/redirect) used to run as the first entry of pipeline.internalMiddleware. It was always pure post-processing — call next(), then inspect the response and maybe redirect / 404 / rewrite. Lift it out of the middleware layer and run it as an explicit step in AstroHandler.render, matching the handler extraction pattern we've been applying to other concerns. - New core/i18n/handler.ts: I18n class constructed with (i18n, base, trailingSlash, format). Builds its own I18nRouter. Exposes finalize(request, response, ctx) containing the body of the old createI18nMiddleware closure. ctx carries the pieces the post-processor needs from an APIContext — redirect, rewrite, currentLocale, isPrerendered — so the class does not depend on the middleware layer. - AstroHandler owns an optional #i18n instance (constructed when app.manifest.i18n is set and strategy is not 'manual'). render(state) runs a local runPipeline() that invokes astroMiddleware.handle and then #i18n.finalize if configured, before the cache-provider wrapping applies cache headers. The rewrite callback threads back through #rewriteAndRender so i18n-triggered fallback rewrites use the same machinery as Astro.rewrite. - base-pipeline.ts no longer pushes createI18nMiddleware into internalMiddleware. The comment explains the move. - i18n/middleware.ts is now a thin shim: the public astro:i18n.middleware(...) API (used for the manual routing strategy) instantiates I18n and wraps it in a MiddlewareHandler closure (await next(), then handler.finalize). Single source of truth for both paths. All tests green: build, 2340 unit, 339 i18n unit, 15 middleware, 13 rewrite, 2 i18n-css-leak, 7 page-format, 7 serializeManifest, and lint:ci pass.
… 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.
…stroHandler.#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.
Contributor
e18e dependency analysisNo dependency warnings found. |
Redirects.handle was async and awaited on every request by AstroHandler.render, even when the route was not a redirect. That introduced a microtask hop between createRenderContext and the rest of the render pipeline, which showed up as a ~22% slowdown on the streaming .md file benchmark (and an 11% slowdown on the hybrid build benchmark) on CodSpeed. Fix: make Redirects.handle synchronous for the non-redirect case. It now returns Promise<Response> | undefined — the Promise from renderRedirect when the route is a redirect, or plain undefined synchronously otherwise. AstroHandler.render checks the return value and only awaits when it's a Promise, so non-redirect routes no longer pay for an extra microtask. All tests green: build, 2344 unit, 4 redirect integration, and lint:ci pass.
27103e6 to
6bd13d6
Compare
Removes the Redirects class in core/redirects/handler.ts. Per-request the hot path previously did a method call on a class field which in turn did a helper call (routeIsRedirect) just to compare routeData.type. Inline the comparison so non-redirect requests cost one property access + string compare. - AstroHandler no longer holds a #redirects: Redirects field. - render() checks routeData.type === 'redirect' directly and calls renderRedirect() from core/redirects/render.js only when needed. All tests green: build, 2344 unit, 4 redirects, 13 rewrite, 15 middleware, lint:ci.
d55ad58 to
ef3db2e
Compare
….handle(state) Move FetchState construction up to DefaultFetchHandler and expand it to own the RenderContext, componentInstance, slots, props, and lazily-built API / action API contexts for a request. Handlers now read per-request data off a single FetchState instead of threading it through signatures. - FetchState now takes a Pipeline (not BaseApp) so the container can build one too. Route matching / dev pathname normalization moves up to AstroHandler. - AstroMiddleware.handle(state, renderRouteCallback) — no more passing renderContext, componentInstance, slots, props, and action context as separate args. - PagesHandler.handle(state, ctx, payload) — reads everything off state; rewrites mutate state.componentInstance and invalidate memoized props / contexts. - RenderContext gets a fetchState back-ref so Rewrites.execute can re-enter middleware with the same per-request state when invoked via Astro.rewrite / ctx.rewrite. - Error handlers and the container build their own FetchState before calling AstroMiddleware.handle. - getAPIContext() is synchronous; callers must await getProps() first (AstroMiddleware does this once on the hot path).
…enderRedirect/I18n.finalize take state AstroHandler.render no longer calls app.createRenderContext or touches RenderContext directly. FetchState owns creation (zero-arg lazy getRenderContext()) and the downstream handlers that previously took a RenderContext now take the FetchState. - FetchState.status field + getRenderContext() that lazily calls RenderContext.create using state (request, pathname, routeData, locals, clientAddress, status). Callers that assign state.renderContext directly (error handlers, container) still get the memoized value. - renderRedirect(state) — reads renderContext off state. - I18n.finalize(state, response) — reads renderContext.computeCurrentLocale, routeData.prerender, and rewrite off state. The I18nFinalizeContext adapter interface is gone. - fetchStateSymbol stashes the active FetchState on APIContext so the manual-strategy i18n middleware wrapper can retrieve it from user- middleware land. - Test updates: test-utils.renderThroughMiddleware builds a FetchState for AstroMiddleware.handle(state); createMockAPIContext stashes a minimal FetchState with a duck-typed renderContext so internal shims keep working; new createMockFetchState helper for redirect unit tests.
FetchState now resolves routes via pipeline.matchRoute() instead of looking up the app via appSymbol on the request. This removes the BaseApp dependency from FetchState and allows the container API to benefit from the same route matching. BaseApp.match() delegates to pipeline.matchRoute() for the low-level pattern match, keeping its own asset/prerender filtering logic.
When the adapter doesn't provide routeData, resolve it via this.match(request) which handles computePathnameFromDomain. pipeline.matchRoute() in FetchState doesn't know about domain routing, so the route must be resolved at the app level.
…TY for bang operator
…decode - #stripHtmlExtension now runs in all modes, not just development (fixes dynamic-route-build-file test) - #resolveRouteData filters prerendered routes in production SSR (fixes ssr-prerender and node adapter prerender 404 tests) - Remove double decodeURI in #resolveRouteData since pathname is already decoded by #computePathname (fixes double-encoded path test) - BaseApp.render() only resolves domain-based i18n routes, not the full match() which filters prerenders
ematipico
reviewed
May 1, 2026
…ting state in redirect tests
ematipico
approved these changes
May 5, 2026
Member
ematipico
left a comment
There was a problem hiding this comment.
Let's ship it :)
There's a test failing we should fix
ArmandPhilippot
approved these changes
May 5, 2026
Member
ArmandPhilippot
left a comment
There was a problem hiding this comment.
Great work to both of you!
I haven't noticed anything that could affect the /experimental-flags/advanced-routing page. But, we'll need a section for cookies.consume() in https://docs.astro.build/en/reference/api-reference/#cookie-utilities
Docs LGTM!
The fallback sentinel (X-Astro-Route-Type: fallback, status 500) signals that the render pipeline couldn't find a page in the current locale. computeFallbackRoute only triggers on 404, so the 500 was silently ignored. Map the sentinel to 404 before passing to computeFallbackRoute.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Changes
Overview of the changes:
Architecture
This change reflects an new architectural approach to request handling in Astro, with the goal of breaking up Astro features like redirects, i18n, and middleware, to be written as composable handlers for those features.
The old architecture looked something like this:
App#render -> RenderContext -> runtime render
With much of the logic for these features either within App#render or RenderContext#render.
The new architecture keeps App as a thin layer for compatibility with the adapter APIs. Adapters can still create instances of App and call App#match and App#render like previous.
The new architecture is:
App#render -> FetchHandler -> Astro features as handlers -> runtime render
Fetch handler
App#render now does very little except calling FetchHandler#fetch. This is the fetch API that users can implement with
src/app.ts:If the user doesn't provide a
src/app.ts, or the experimental feature is not enabled, we use theDefaultFetchHandler. This handler just creates aFetchStateinstance and callsastro(state).FetchState
The
FetchStateobject is an object that holds state for the request. It contains data such as therouteData,pathname, theresponse, and the APIContext, among other things.Handler functions can mutate this object to change the state.
This object replaces the
RenderContextobject. It's mostly the same object, but renamed to be user-facing and to add some more state that wasn't part of the RenderContext.The object is exposed so that users can create instances of FetchState to pass into the handler functions like
astro(state).Handlers
This PR adds handlers for Astro features as described in the RFC.
The AstroHandler is the main handler at core/routing/handler.ts and it composes Actions, i18n, pages, etc. to provide the normal request handling experience of Astro. It's exposed as the
astro()function onastro/fetchandastro/hono.In general this follows a pattern of
{feature}/handler.ts, so you'll see newhandler.tsfiles for various features. Most of these take aFetchStateobject and either return a Response or undefined.Some like middleware take a callback function since they need to wrap request handling in order to call middleware with the response for post-processing.
App / Pipeline / RenderOptions
Much of the internals of Astro depend on either an
Appor aPipeline. In the new architecture theAppis added to theRequestvia a symbol which is what allows the new handlers to have access to thePipelineobject.This is not ideal and it means if user code does
new Request('/'), that object will not have the App and things will break. It's uncommon for a user to want to do that, but long term we'll want to remove the dependency on App and Pipeline and instead depend directly on the Manifest, which is globally available.API
The
virtual:astro:fetchablevirtual module is what loads the user'ssrc/app.tswhen the experimental flag is enabled or falls back to theDefaultFetchHandler.Testing
Docs