Experimental advanced routing
هذا المحتوى غير متوفر بلغتك بعد.
Type: boolean
Default: false
astro@6.3.0
جديد
Enables src/app.ts as a custom request pipeline entrypoint, giving you full control over how Astro handles incoming requests.
By default, Astro handles every request with a built-in pipeline that runs trailing-slash normalization, redirects, sessions, actions, user middleware, page rendering, i18n, and caching in a fixed order. Advanced routing lets you replace this pipeline with your own, composing Astro’s built-in handler functions with custom logic in any order you choose.
import { defineConfig } from 'astro/config';
export default defineConfig({ experimental: { advancedRouting: true, },});Creating src/app.ts
Section titled “Creating src/app.ts”When advancedRouting is enabled, create a src/app.ts (or .js, .mjs, .mts) file that default-exports an object with a fetch method. The fetch method receives a standard Request and must return a Response.
If no src/app.ts file exists (or advancedRouting is not enabled), Astro uses its built-in pipeline, which runs all features automatically.
Using astro()
Section titled “Using astro()”The easiest way to get started is with the astro() handler, which runs Astro’s full built-in pipeline (sessions, cache, redirects, trailing-slash, actions, middleware, pages, and i18n) in the default order. This lets you add custom logic before or after Astro without changing how the internal pipeline works:
import { FetchState, astro } from 'astro/fetch';
export default { async fetch(request: Request): Promise<Response> { const state = new FetchState(request);
// Custom pre-processing, runs before any Astro handler const url = new URL(request.url); if (url.pathname.startsWith('/dashboard')) { const cookie = request.headers.get('cookie') ?? ''; if (!cookie.includes('session=')) { return new Response(null, { status: 302, headers: { Location: '/login' }, }); } }
const response = await astro(state);
// Custom post-processing, runs after Astro renders response.headers.set('X-Powered-By', 'Astro'); return response; },};For many use cases, such as adding auth guards, request logging, and custom headers, astro() is all you need.
Composing individual handlers
Section titled “Composing individual handlers”When you need more control over the pipeline order, or want to omit certain features entirely, you can compose individual handler functions from astro/fetch:
import { FetchState, sessions, actions, middleware, pages, i18n,} from 'astro/fetch';
export default { async fetch(request: Request): Promise<Response> { const state = new FetchState(request); await sessions(state); try { const actionResponse = await actions(state); if (actionResponse) return actionResponse;
const response = await middleware(state, (s) => pages(s)); return i18n(state, response); } finally { await state.finalizeAll(); } },};Each function operates on a FetchState object that tracks per-request data like the matched route, cookies, and session. You can call them in any order and mix them with your own logic.
Adding custom logic
Section titled “Adding custom logic”The main benefit of advanced routing is the ability to insert custom logic anywhere in the request pipeline. You can run code before Astro touches the request, between pipeline stages, or after the response is produced.
The astro() handler is the simplest way to add pre- or post-processing around the full pipeline. When you need to insert logic between specific stages, such as running custom code after actions but before page rendering, compose the individual handlers instead:
import { FetchState, actions, middleware, pages, i18n,} from 'astro/fetch';
export default { async fetch(request: Request): Promise<Response> { const state = new FetchState(request);
const actionResponse = await actions(state); if (actionResponse) return actionResponse;
// Custom logic between actions and page rendering console.log(`Rendering ${new URL(request.url).pathname}`);
const response = await middleware(state, (s) => pages(s)); return i18n(state, response); },};astro/fetch handler reference
Section titled “astro/fetch handler reference”All handler functions are imported from astro/fetch and operate on a FetchState object.
FetchState
Section titled “FetchState”The per-request state object. Create one at the start of your fetch method:
import { FetchState } from 'astro/fetch';
const state = new FetchState(request);FetchState tracks the matched route, cookies, session providers, and other per-request data. All handler functions require it as their first argument.
Properties
Section titled “Properties”state.request
Section titled “state.request”Type: Request
The incoming Request object.
state.url
Section titled “state.url”Type: URL
A normalized URL derived from the request.
state.pathname
Section titled “state.pathname”Type: string
The base-stripped, decoded pathname of the request (e.g. /about or /blog/my-post).
state.routeData
Section titled “state.routeData”Type: RouteData | undefined
The matched route for this request, if any. This is resolved automatically when the FetchState is created.
state.cookies
Section titled “state.cookies”Type: AstroCookies
An AstroCookies instance for reading and setting cookies on this request.
state.locals
Section titled “state.locals”Type: App.Locals
A request-scoped object for storing custom data. This is the same locals object available in middleware and API routes.
state.params
Section titled “state.params”Type: Params | undefined
Route parameters derived from the matched route and pathname (e.g. { slug: 'my-post' } for a [slug].astro route).
state.status
Section titled “state.status”Type: number
Default: 200
The HTTP status code for the response. You can set this before rendering to control the response status (e.g. state.status = 404).
Methods
Section titled “Methods”state.rewrite()
Section titled “state.rewrite()”Type: (payload: RewritePayload) => Promise<Response>
Triggers a rewrite to a different route. The payload can be a pathname string ('/other-page'), a URL, or a Request:
const response = await state.rewrite('/other-page');state.provide()
Section titled “state.provide()”Type: (key: string, provider: ContextProvider<T>) => void
Registers a context provider under the given key. The provider’s create() function is called lazily on the first state.resolve(), and its optional finalize() callback runs during state.finalizeAll():
state.provide('myService', { create: () => new MyService(), finalize: (service) => service.close(),});state.resolve()
Section titled “state.resolve()”Type: (key: string) => T | undefined
Returns the value for a previously registered provider, calling its create function on first access. Returns undefined if no provider was registered for the key:
const service = state.resolve('myService');state.finalizeAll()
Section titled “state.finalizeAll()”Type: () => Promise<void> | void
Runs all registered provider finalize callbacks. Call this after the response is produced, typically in a finally block, to persist session data and clean up resources:
await sessions(state);try { // ...render pipeline... return response;} finally { await state.finalizeAll();}astro()
Section titled “astro()”Type: (state: FetchState) => Promise<Response>
The all-in-one handler that runs the full Astro pipeline (sessions, cache, redirects, trailing-slash, actions, middleware, pages, and i18n) in the default order. Use this when you want to add logic before or after Astro without changing the internal pipeline order:
import { FetchState, astro } from 'astro/fetch';
export default { async fetch(request: Request): Promise<Response> { const state = new FetchState(request); // custom pre-processing here... const response = await astro(state); // custom post-processing here... return response; },};pages()
Section titled “pages()”Type: (state: FetchState) => Promise<Response>
Dispatches the request to the matched Astro route (page, endpoint, or fallback). This is the core rendering handler, and most custom pipelines will include it.
middleware()
Section titled “middleware()”Type: (state: FetchState, next: (state: FetchState) => Promise<Response>) => Promise<Response>
Runs Astro’s middleware chain (from src/middleware.ts). The next callback is called at the bottom of the chain to produce the response, typically by calling pages():
const response = await middleware(state, (s) => pages(s));actions()
Section titled “actions()”Type: (state: FetchState) => Promise<Response | undefined> | undefined
Handles Astro Actions (RPC and form submissions). Returns a Response for RPC actions, or undefined for form actions and non-action requests. Check the return value to decide whether to continue rendering:
const actionResponse = await actions(state);if (actionResponse) return actionResponse;// otherwise continue to page rendering...sessions()
Section titled “sessions()”Type: (state: FetchState) => Promise<void> | void
Registers the session provider. Sessions are created lazily when your code accesses ctx.session and persisted when state.finalizeAll() is called. Call this early in the pipeline, and call finalizeAll() in a finally block:
await sessions(state);try { // ...render pipeline...} finally { await state.finalizeAll();}i18n()
Section titled “i18n()”Type: (state: FetchState, response: Response) => Promise<Response>
Post-processes a response against your i18n configuration. Handles locale redirects, 404s for invalid locales, and fallback routing. Call this after rendering:
const response = await middleware(state, (s) => pages(s));return i18n(state, response);redirects()
Section titled “redirects()”Type: (state: FetchState) => Promise<Response> | undefined
Handles redirect routes defined in your Astro config. Returns a redirect Response if the matched route is a redirect, or undefined if the caller should continue processing.
cache()
Section titled “cache()”Type: (state: FetchState, next: () => Promise<Response>) => Promise<Response>
Wraps a render callback with cache provider logic. Handles runtime caching, CDN-based providers, and the no-cache case.
trailingSlash()
Section titled “trailingSlash()”Type: (state: FetchState) => Response | undefined
Checks if the request pathname needs trailing-slash normalization and returns a redirect Response if so. Returns undefined when no redirect is needed.
Using with Hono
Section titled “Using with Hono”Astro also provides Hono-compatible wrappers for all handler functions via astro/hono. If you prefer to use Hono as your routing framework, you can export a Hono app from src/app.ts:
import { Hono } from 'hono';import { logger } from 'hono/logger';import { actions, middleware, pages, i18n } from 'astro/hono';
const app = new Hono();
// Hono middlewareapp.use(logger());
// Astro handlers (as Hono middleware)app.use(actions());app.use(middleware());app.use(pages());app.use(i18n());
export default app;The astro/hono module exports the same handler names as astro/fetch (astro, pages, middleware, actions, sessions, redirects, cache, i18n, trailingSlash), but each returns a Hono middleware function. This lets you mix Astro handlers with any Hono middleware from the ecosystem.