From static sites to full-stack apps in one deployment

Cloudflare has extended Workers with static asset hosting in beta form since September 2024, but a series of new releases now consolidates the platform into a single home for frontend, backend, and database work. The net effect: you can build and deploy anything from a plain HTML site to a server-rendered application with an API and a database connection without stitching together separate Cloudflare products.

Ten upgrades shipped together to make this possible:

  • General availability for major frameworks. React Router v7 (Remix), Astro, Hono, Vue.js, Nuxt, and Svelte (SvelteKit) are now production-ready on Workers. Next.js, Angular, and SolidJS (SolidStart) are slated for GA in Q2 2025.
  • Framework-free full-stack development. Pair Vite with React and add a backend API in the same Worker; a Vite + React template demonstrates the pattern.
  • Next.js adapter maturity. @opennextjs/cloudflare is now at v1.0-beta and approaching GA. It will support the newly announced Next.js Deployments API.
  • Cloudflare Vite plugin v1.0. The plugin runs Vite's dev server inside workerd, keeping Hot Module Replacement while unlocking Workers-only features like Durable Objects.
  • Pages-style config files on Workers. _headers and _redirects files now work for static assets on Workers, enabling header and redirect rules without Worker code execution.
  • MySQL support via Hyperdrive. Beyond PostgreSQL, Hyperdrive now connects Workers to MySQL databases — including those hosted on Planetscale, AWS, GCP, and Azure — with connection pooling and query caching.
  • Expanded Node.js compatibility. APIs from crypto, tls, net, and dns modules are available in the Workers runtime. Maximum CPU time per request rises from 30 seconds to 5 minutes.
  • Workers Builds from any repo. Connect a GitHub or GitLab repository containing a Worker application and Workers Builds deploys it automatically, with builds starting up to 6 seconds faster.
  • CI/CD improvements. Builds can run on non-production branches, and preview URLs are posted back to GitHub as pull request comments.
  • Images binding GA. The Images binding in Workers is now generally available for programmatic image workflows.

Billing remains usage-based: you pay only when Worker code executes, so static sites hosted on Workers are free. Server-side rendering or an API only costs when a Worker actually runs, and data storage is handled by Hyperdrive for existing databases or by Workers KV, R2, Durable Objects, or D1 for new ones.

A deployable single-page application built with Vite and React, with an optional Hyperdrive connection to a hosted database, is available via the template below:

Deploy to Cloudflare

Starting on Workers instead of Pages

The historical split between Cloudflare Pages and Workers forced an early architectural decision. If you chose Pages for its streamlined developer experience, you could later hit limits — Durable Objects required a separate Worker, real-time logs were unavailable, and rollouts were all-or-nothing. If you chose Workers, you missed out on easy static asset hosting.

With static assets now supported on Workers, that trade-off disappears. Cloudflare is directing its platform investment toward Workers, while Pages continues to operate. New Workers projects get the full Developer Platform binding surface — Durable Objects, Email Workers, and more — within a single project and single deployment. Observability tooling like Workers Logs is built in, and Gradual Deployments allow traffic-shifted rollouts.

The Pages features developers relied on are being folded into Workers:

  • Static _headers and _redirects config files, so existing Pages projects (or those from other platforms) can move over without restructuring.
  • Native GitHub and GitLab integration via Workers Builds for automatic builds and deployments.
  • Preview URLs posted directly to repository pull requests, with feature branch aliases and environments planned.

For teams with an existing Pages project, Cloudflare provides a migration guide covering the move to Workers.

Rendering models on Workers: static, SPA, and SSR

Workers support three primary architectures for serving web content, each with different trade-offs between build-time preparation and request-time rendering:

  • Static sites return pre-built HTML, CSS, JavaScript, images, and fonts directly from a CDN, with no server-side rendering at request time. Content is generated at build time and served as-is, which suits sites whose content changes infrequently.
  • Single-page applications (SPAs) ship a minimal HTML shell plus a JavaScript bundle. The browser downloads the bundle and renders the entire UI client-side; subsequent navigation happens via client-side routing without full page reloads.
  • Server-side rendered (SSR) applications generate complete HTML on the server for each request. The browser displays this immediately, then JavaScript "hydrates" the page to add interactivity. Subsequent navigations can either request new server-rendered pages or transition to client-side rendering.

Project setup with the Vite plugin

Wrangler handles bundling when you run wrangler dev, but Cloudflare now also provides a Vite plugin for teams already using Vite's tooling. This lets you keep using Vite's dev server and test with Vitest, all on the Workers runtime. To scaffold a React app with the plugin:

npm create cloudflare@latest my-react-app -- --framework=react

After scaffolding, the project layout looks like:

...
├── api
│   └── index.ts
├── public
│   └── ...
├── src
│   └── ...
...
├── index.html
├── package.json
├── vite.config.ts
└── wrangler.jsonc

Running npm run build creates a new /dist directory:

...
├── api
│   └── index.ts
├── dist
│   └── ...
├── public
│   └── ...
├── src
│   └── ...
...
├── index.html
├── package.json
├── vite.config.ts
└── wrangler.jsonc

The Vite plugin tells Wrangler that /dist holds the built static assets — client code, CSS, and images. The deployed architecture looks like this:

BLOG-2710 Image 2

At runtime, Cloudflare matches the request pathname against files in the assets directory. A request for example.com/blog will serve blog.html if that file exists.

Static sites from an SSG

For a static site generated with a tool like Astro, you only need a wrangler.jsonc (or wrangler.toml) pointing at your built output:

// wrangler.jsonc 

{
  "name": "my-static-site",
  "compatibility_date": "2025-04-01",
  "assets": {
    "directory": "./dist",
  }
}

Build the project, run wrangler deploy, and the site is live — with caching across Cloudflare's network applied automatically:

BLOG-2710 Image 3

To start a fresh Astro project on Workers:

npm create cloudflare@latest my-astro-app -- --framework=astro

Other supported frameworks and their setup instructions are listed in the framework guides.

SPA mode with a Worker backend

For single-page applications, enable single-page-application mode in the Wrangler configuration:

{
 "name": "example-spa-worker-hyperdrive",
 "main": "api/index.js",
 "compatibility_flags": ["nodejs_compat"],
 "compatibility_date": "2025-04-01",
 },
 "assets": {
   "directory": "./dist",
   "binding": "ASSETS",
   "not_found_handling": "single-page-application"
 },
 "hyperdrive": [
   {
     "binding": "HYPERDRIVE",
     "id": "d9c9cfb2587f44ee9b0730baa692ffec",
     "localConnectionString": "postgresql://myuser:mypassword@localhost:5432/mydatabase"
   }
 ],
 "placement": {
   "mode": "smart"
 }
}

In this mode, navigation requests (those carrying a Sec-Fetch-Mode: navigate header) that don't match a static asset get served index.html. Non-navigation requests — for data, for instance — that miss the asset directory are routed to the Worker script. This lets you render the frontend with React, run backend logic in the Worker, and let Vite stitch the pieces together. It's a practical migration path for older SPAs built with create-react-app, which was recently sunset.

The same config file also defines a Hyperdrive binding and enables Smart Placement. Hyperdrive bridges Workers to existing databases by handling connection pooling — a significant issue given that Workers run in lightweight V8 isolates without persistent TCP sockets, strict CPU and memory limits, and no way to hold open database connections themselves. Hyperdrive maintains stable connections on behalf of Workers. Smart Placement further reduces latency by relocating both the Worker and the Hyperdrive "bridge" closer to the database when requests originate far from it.

SPA example: Worker code

In the "Deploy to Cloudflare" example, api/index.js defines a Hono-based API that connects to a hosted database through Hyperdrive:

import { Hono } from "hono";
import postgres from "postgres";
import booksRouter from "./routes/books";
import bookRelatedRouter from "./routes/book-related";

const app = new Hono();

// Setup SQL client middleware
app.use("*", async (c, next) => {
 // Create SQL client
 const sql = postgres(c.env.HYPERDRIVE.connectionString, {
   max: 5,
   fetch_types: false,
 });

 c.env.SQL = sql;

 // Process the request
 await next();

 // Close the SQL connection after the response is sent
 c.executionCtx.waitUntil(sql.end());
});

app.route("/api/books", booksRouter);
app.route("/api/books/:id/related", bookRelatedRouter);

export default {
 fetch: app.fetch,
};

The deployed architecture looks like this:

BLOG-2710 Image 1

With Smart Placement active, the Worker and Hyperdrive may relocate closer to the database:

Server-side rendering with full-stack frameworks

For server-rendered pages, Workers supports several popular full-stack frameworks. This example uses React Router v7 with SSR:

Deploy to Cloudflare

Next.js via the OpenNext adapter is also an option, as are the other frameworks listed in the framework guides.

Porting existing apps with minimal changes

Node.js compatibility improvements

Workers recently added support for the crypto, tls, net, and dns Node.js modules, allowing libraries that depend on them to run unmodified. For example, the mongodb package previously failed because it used node:dns for hostname lookups:

Error: [unenv] dns.resolveTxt is not implemented yet!

Even past that error, mongodb would then fail when using node:tls to establish a secure database connection. Both modules are now supported, as are node:crypto and node:net for other libraries.

Additionally, Workers expose environment variables and secrets on process.env when the nodejs_compat flag is enabled and the compatibility date is on or after 2025-04-01. Many libraries and developers expect this object to be populated at module load time, and its absence broke top-level configuration logic. With this change, variables become accessible exactly as in Node.js:

const LOG_LEVEL = process.env.LOG_LEVEL || "info";

Higher CPU time limits

The maximum CPU time per Worker request has been raised from 30 seconds to 5 minutes. Compute-intensive work such as hashing a large file with node:crypto can now run within a Worker instead of being offloaded to external infrastructure.

Workers Builds improvements

Workers Builds, introduced at Builder Day 2024, connects a Git repository to a Worker for automatic builds and deployments on every push. The feature originally required connecting a repository to an existing Worker; now you can deploy a repository directly as a new Worker. Build starts have also gotten faster — latency dropped by 6 seconds, with builds now starting in 10 seconds on average — and API response times improved 7x thanks to Smart Placement.

  • Pricing note: On April 2, 2025, Workers Builds moved to a new pricing model announced at Builder Day 2024. Free plans cap at 3,000 build minutes; Workers Paid subscribers get a usage-based model with 6,000 free minutes/month and $0.005 per build minute afterward. Paid plans now include six concurrent builds to support monorepos and multi-project work. Full details are in the documentation.

Workers Builds can also run on non-production branches, posting preview URLs back to GitHub as pull request comments.

Binding the Images API

The Images binding adds programmatic image optimization to Workers. Previously, optimization required calling fetch() with a publicly accessible image URL. The binding works directly on an image body as a byte stream, which matters when source images aren't URL-addressable — for example, compressing a user upload before persisting it to storage. See the guide on transforming an image before upload to R2 for details.

Building on Workers: Getting Started

With static assets, frontend components, and backend logic now unified on Workers, you can begin assembling your application immediately. The platform is positioned for continuous expansion, with new capabilities planned to simplify development across the entire stack.

Community input has shaped much of the recent progress, and the Cloudflare Developers Discord server is the primary channel for ongoing discussion and feedback on these features.

Key Development Resources

  • Framework guides: Reference implementations and setup instructions for popular frontend frameworks that are compatible with Workers static assets.
  • Migration guide: If you are transitioning from Cloudflare Pages, this documentation covers the steps for moving existing projects to the Workers platform.
  • Static assets documentation: The full API reference and configuration details for serving files and building asset-aware Workers.
  • Cloudflare Vite plugin documentation: Guidance for leveraging the Vite plugin to streamline local development and deployment workflows.