跳转到内容

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.

astro.config.mjs
import { defineConfig } from 'astro/config';
export default defineConfig({
experimental: {
advancedRouting: true,
},
});

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.

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:

src/app.ts
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.

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:

src/app.ts
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.

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:

src/app.ts
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);
},
};

All handler functions are imported from astro/fetch and operate on a FetchState object.

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.

Type: Request

The incoming Request object.

Type: URL

A normalized URL derived from the request.

Type: string

The base-stripped, decoded pathname of the request (e.g. /about or /blog/my-post).

Type: RouteData | undefined

The matched route for this request, if any. This is resolved automatically when the FetchState is created.

Type: AstroCookies

An AstroCookies instance for reading and setting cookies on this request.

Type: App.Locals

A request-scoped object for storing custom data. This is the same locals object available in middleware and API routes.

Type: Params | undefined

Route parameters derived from the matched route and pathname (e.g. { slug: 'my-post' } for a [slug].astro route).

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).

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');

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(),
});

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');

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();
}

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:

src/app.ts
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;
},
};

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.

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));

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...

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();
}

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);

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.

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.

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.

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:

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 middleware
app.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.

贡献 社区 赞助