Functions, bindings, and operations
Add one Pages Function
Create a file-based request handler and understand that Pages Functions execute on the Workers runtime.
Pages Functions add server code to a static site. You create a functions/ directory next to your build output, and every file in it becomes a route. functions/api/health.js answers /api/health. functions/contact.js answers /contact.
Under the hood a Function is a Worker. Same runtime, same Request and Response objects, same bindings. Pages just wires the routing for you based on file names.
The handler
A file exports an onRequest function. It receives a context object with the request, the environment bindings, route parameters, and a next function for middleware.
Create one narrow function and keep static files outside its route:
export function onRequest() {
return Response.json({ ok: true })
}
Save it as functions/api/health.js, deploy, and request /api/health. Then request a CSS file and verify it remains static. This check matters because an overly broad middleware route can turn every asset request into a metered function invocation.
You can also export per-method handlers. onRequestGet only runs for GET, onRequestPost only for POST. Other methods skip that handler, so you don’t write the method check yourself.
Run it locally before deploying:
npx wrangler pages dev dist
# ⎔ Starting local server...
# [wrangler:inf] Ready on http://localhost:8788
curl http://localhost:8788/api/health
# {"ok":true}
Why the static check matters
Pages serves static files directly, for free, without running your code. A Function only runs when a request matches one of its routes. That’s the whole cost model.
The trap is a file named functions/_middleware.js. It matches every route, including every CSS file and image. Suddenly each asset request becomes a metered function invocation, and your bill and failure surface both grow for no reason.
If you need middleware, scope it. Put it under functions/api/_middleware.js so it only wraps the API routes, or ship a _routes.json file in the output directory that excludes static paths.
Return real statuses and content types
A health endpoint that returns 200 with a text body is fine. An API that returns 200 with an error message inside JSON is not. Use Response.json() for JSON bodies so the content type is right, and pick the status code that matches what happened:
export function onRequestPost() {
return Response.json({ error: 'name is required' }, { status: 400 })
}
Clients, monitors, and caches all read the status before the body.
Try this: deploy the health Function, request /api/health and one CSS file on the deployed site, and confirm only the first one shows up in the Functions logs.
Lesson completed