Pages Functions route by file path; _middleware.js wraps the rest

functions/api/[id].js answers /api/:id through onRequestGet and friends; context.env holds bindings; _middleware.js runs first and calls next().

Pages

· Chapter

37

·

3

min read

The answer. Everything under functions/ becomes one Worker at build time, routed by filename. functions/hello.js answers /hello; folders are path segments; [id].js captures a dynamic segment into context.params.id; a double-bracketed path.js captures the rest of the path. Export onRequest to catch every method or onRequestGet, onRequestPost and friends per verb. The handler receives one context object: request, env (bindings, not process.env), params, waitUntil, and next(). A _middleware.js file applies to its directory and everything beneath it, so a root middleware sees every request and decides whether to call next(), which falls through to the next function or, at the end, to the static asset. _routes.json narrows which paths invoke Functions at all, which is how you keep static traffic free.

The pattern.

functions/
  _middleware.js        -> runs on every request (auth, content negotiation, headers)
  api/
    images.js           -> /api/images        exports onRequestGet, onRequestPost
    images/[id].js      -> /api/images/:id    context.params.id
  mcp.js                -> /mcp
// functions/api/images/[id].js
export async function onRequestGet({ env, params }) {
  const row = await env.DB.prepare("select * from images where id = ?").bind(params.id).first();
  return row ? Response.json(row) : new Response("not found", { status: 404 });
}
// functions/_middleware.js
export async function onRequest({ request, next }) {
  const res = await next();
  res.headers.set("x-frame-options", "DENY");
  return res;
}

Watch out.

  • wrangler pages dev ./public (port 8788) is the only local mode that runs Functions; a framework dev server does not.
  • Bindings for Functions are configured in the Pages project (or a wrangler.toml with pages_build_output_dir), and preview and production each need their own.
  • Frameworks (Next.js on Pages) compile their own route handlers to Functions and must run in the edge runtime.

Related: pages-middleware-accept-text-markdown-negotiation · mcp-server-as-pages-function-stateless-streamable-http · pages-vs-workers-static-assets-start-new-on-workers