Full-stack Pages: adding serverless functions to static sites

Cloudflare Pages now supports full-stack applications, letting you add serverless functions directly to your Pages project with a single new file. These functions tap into Cloudflare Workers, enabling dynamic rendering, calls to external APIs, and persistence through KV and Durable Objects without leaving the Pages development workflow.

To demonstrate what's possible, Cloudflare published an example image-sharing platform. It combines a JSON API built with Pages Functions, data stored in KV and Durable Objects, integrations with Cloudflare Images and Access, and a React front end. The live demo is at images.pages.dev and the source code is available on GitHub.

How Pages Functions work

Pages Functions follow file-based routing. A function placed at ./functions/time.js will automatically be served at /time, with all other requests falling back to your project's static assets. Placing them in directories works the same way: ./functions/api/time.js maps to /api/time, and ./functions/some_directory/index.js maps to /some_directory.

Beyond plain JavaScript, you can use TypeScript (./functions/time.ts) and parameterized routes. Single square brackets, as in ./functions/todos/[id].js, match any single path segment like /todos/123. Double square brackets, as in ./functions/todos/[[path]].js, match any number of path segments, such as /todos/123/subtasks. For type checking, the PagesFunction type is available in the @cloudflare/workers-types library.

Working with dynamic data and external services

Functions handle different HTTP methods by exporting named handlers. For example, exporting onRequestGet restricts the endpoint to GET requests. In the sample app, a KV namespace bound as env.IMAGES stores metadata about uploaded images, and a dedicated endpoint reads that data to display images on the homepage. KV bindings are configured under the "Settings" tab of your Pages project in the Cloudflare dashboard.

Serverless functions also make it straightforward to interact with other Cloudflare services. To upload images, the app includes a function that accepts incoming files and forwards them to the Cloudflare Images API, where you can define different variants for rendering and use signed URLs for access control.

Adding synchronized state with Durable Objects

KV is well suited for data that is read frequently but written rarely. For features that need more synchronization—such as a per-image download counter—the sample app turns to Durable Objects. A Durable Object class is created to maintain the download count, and an endpoint increments the counter whenever a user requests a high-resolution download link.

Protecting routes with middleware

For code that should run before a function, Pages supports middleware through a _middleware.ts file. When one is placed in a directory, it runs first, and the function executes when next() is called. This works at any level of your routing hierarchy.

The sample app uses middleware to protect admin-only endpoints like /api/admin/upload and /api/admin/delete. A ./functions/api/admin/_middleware.ts file wraps all requests under /api/admin/* with Cloudflare Access, providing role-based access control. The same pattern can be used for observability and error logging tools like Honeycomb and Sentry.

The front end and local development

The "Jam" in Jamstack stands for JavaScript, API, and Markup. Pages previously covered the JavaScript and Markup; with Functions, you can now handle the API layer entirely on Cloudflare's network as well.

For demonstration purposes, the sample app's front end is built with Create React App, but Pages natively integrates with more than 20 frameworks and supports custom build commands. The front end fetches from the Functions you define—the sample uses SWR, though plain fetch calls work just as well.

Iterating locally is supported through a first-class integration with Wrangler. It covers Functions, Workers, secrets, environment variables, and KV for Pages projects, with Durable Objects on the way. Install it from npm and either point it at a folder of static assets or proxy it to your existing local development server.