Middleware and Lifecycle
Handle requests, and run code at startup and after the build.
Middleware
middleware runs first on every request your server receives. Call next() to continue, or return a Response to stop. This plugin protects a prefix with a token:
async middleware(request, next) {
const { pathname } = new URL(request.url);
if (
pathname.startsWith(this.withBase(options.prefix)) &&
request.headers.get("authorization") !== `Bearer ${options.token}`
)
return new Response("Unauthorized", { status: 401 });
return next();
},Changing the Response
Wait for next(), then return a copy with your changes:
async middleware(_request, next) {
const response = await next();
const out = new Response(response.body, response);
out.headers.set("x-powered-by", "acme");
return out;
},Rewrites
next(path) renders another path instead, with the URL unchanged. The path doesn't include the base path.
middleware(request, next) {
const { pathname } = new URL(request.url);
return pathname === this.withBase("/latest") ? next("/v2") : next();
},Requests That Reach Middleware
middleware runs for the routes rendered on demand. Prerendered pages and the files of public/ never reach it.
A build without an adapter warns that the middleware of your plugins never runs.
next(path) can render pages from dynamic sources, plugin views and API routes. For prerendered
files and your .astro pages, it responds with an empty 404.
See Authentication for the routes that a check should cover.
Lifecycle Hooks
These hooks run in this order, once per site:
init()Before the content loader exists. Read files your other hooks need.
configureLoader()Adds options to the content loader, like its plugins.
configure()After the loader is created. Start watchers and other long-running work.
build(output)Runs after a build, not on dev or production servers.
Reading Files at Startup
The Mintlify plugin reads its docs.json like this, so that other hooks have it:
import { readFile } from "node:fs/promises";
export function acmePlugin<C extends AppShape = AppShape>(): Plugin<C> {
let config: { title: string };
return {
name: "acme",
async init() {
config = JSON.parse(await readFile("acme.json", "utf8"));
},
layoutProps: () => ({ nav: { title: config.title } }),
};
}Loader Options
configureLoader adds to the Fumadocs loader options. Loader plugins from all your plugins are combined. Any other option can be set by one plugin only, and the options in createPress() take priority.
import { lucideIconsPlugin } from "fumadocs-core/source/plugins/lucide-icons";
configureLoader() {
return { plugins: [lucideIconsPlugin()] };
},Watching Files
this.signal aborts when press.config.tsx reloads, and this.invalidateLoader(source) reloads a dynamic source. This plugin uses them to watch a folder:
import { watch } from "node:fs";
export function notesPlugin<C extends AppShape = AppShape>(options: {
dir: string;
source: C["source"];
}): Plugin<C> {
return {
name: "acme:notes",
configure() {
if (!import.meta.env.DEV) return;
watch(options.dir, { recursive: true, signal: this.signal }, () =>
this.invalidateLoader(options.source),
);
},
};
}Post-Build Hook
build receives the links of prerendered pages, fetch to request a built page, and emitFile to write to the output. Throw to fail the build.
async build({ links, emitFile }) {
await emitFile("links.json", JSON.stringify(links));
},The build hook only receives the pages that Astro prerenders. See Post-Build
Plugins.
Last updated on