
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/">
    <channel>
        <title><![CDATA[ The Cloudflare Blog ]]></title>
        <description><![CDATA[ Get the latest news on how products at Cloudflare are built, technologies used, and join the teams helping to build a better Internet. ]]></description>
        <link>https://blog.cloudflare.com</link>
        <atom:link href="https://blog.cloudflare.com/" rel="self" type="application/rss+xml"/>
        <language>en-us</language>
        <image>
            <url>https://blog.cloudflare.com/favicon.png</url>
            <title>The Cloudflare Blog</title>
            <link>https://blog.cloudflare.com</link>
        </image>
        <lastBuildDate>Thu, 09 Apr 2026 20:34:49 GMT</lastBuildDate>
        <item>
            <title><![CDATA[Building vertical microfrontends on Cloudflare’s platform]]></title>
            <link>https://blog.cloudflare.com/vertical-microfrontends/</link>
            <pubDate>Fri, 30 Jan 2026 14:00:00 GMT</pubDate>
            <description><![CDATA[ Deploy multiple Workers under a single domain with the ability to make them feel like single-page applications. We take a look at how service bindings enable URL path routing to multiple projects. ]]></description>
            <content:encoded><![CDATA[ <p><i>Updated at 6:55 a.m. PT</i></p><p>Today, we’re introducing a new Worker template for Vertical Microfrontends (VMFE). <a href="https://dash.cloudflare.com/?to=/:account/workers-and-pages/create?type=vmfe"><u>This template</u></a> allows you to map multiple independent <a href="https://workers.cloudflare.com/"><u>Cloudflare Workers</u></a> to a single domain, enabling teams to work in complete silos — shipping marketing, docs, and dashboards independently — while presenting a single, seamless application to the user.</p><a href="https://dash.cloudflare.com/?to=/:account/workers-and-pages/create?type=vmfe"><img src="https://deploy.workers.cloudflare.com/button" /></a>
<p></p><p>Most microfrontend architectures are "horizontal", meaning different <i>parts</i> of a single page are fetched from different services. Vertical microfrontends take a different approach by splitting the application by URL path. In this model, a team owning the `/blog` path doesn't <i>just</i> own a component; they own the entire vertical stack for that route – framework, library choice, CI/CD and more. Owning the entire stack of a path, or set of paths, allows teams to have true ownership of their work and ship with confidence.</p><p>Teams face problems as they grow, where different frameworks serve varying use cases. A marketing website could be better utilized with Astro, for example, while a dashboard might be better with React. Or say you have a monolithic code base where many teams ship as a collective. An update to add new features from several teams can get frustratingly rolled back because a single team introduced a regression. How do we solve the problem of obscuring the technical implementation details away from the user and letting teams ship a cohesive user experience with full autonomy and control of their domains?</p><p>Vertical microfrontends can be the answer. Let’s dive in and explore how they solve developer pain points together.</p>
    <div>
      <h2>What are vertical microfrontends?</h2>
      <a href="#what-are-vertical-microfrontends">
        
      </a>
    </div>
    <p>A vertical microfrontend is an architectural pattern where a single independent team owns an entire slice of the application’s functionality, from the user interface all the way down to the <a href="https://www.cloudflare.com/learning/serverless/glossary/what-is-ci-cd/">CI/CD pipeline</a>. These slices are defined by paths on a domain where you can associate individual Workers with specific paths:</p>
            <pre><code>/      = Marketing
/docs  = Documentation
/blog  = Blog
/dash  = Dashboard</code></pre>
            <p>We could take it a step further and focus on more granular sub-path Worker associations, too, such as a dashboard. Within a dashboard, you likely segment out various features or products by adding depth to your URL path (e.g. <code>/dash/product-a</code>) and navigating between two products could mean two entirely different code bases. </p><p>Now with vertical microfrontends, we could also have the following:</p>
            <pre><code>/dash/product-a  = WorkerA
/dash/product-b  = WorkerB</code></pre>
            <p>Each of the above paths are their own frontend project with zero shared code between them. The <code>product-a</code> and <code>product-b</code> routes map to separately deployed frontend applications that have their own frameworks, libraries, CI/CD pipelines defined and owned by their own teams. FINALLY.</p><p>You can now own your own code from end to end. But now we need to find a way to stitch these separate projects together, and even more so, make them feel as if they are a unified experience.</p><p>We experience this pain point ourselves here at Cloudflare, as the dashboard has many individual teams owning their own products. Teams must contend with the fact that changes made outside their control impact how users experience their product. </p><p>Internally, we are now using a similar strategy for our own dashboard. When users navigate from the core dashboard into our ZeroTrust product, in reality they are two entirely separate projects and the user is simply being routed to that project by its path <code>/:accountId/one</code>.</p>
    <div>
      <h2>Visually unified experiences</h2>
      <a href="#visually-unified-experiences">
        
      </a>
    </div>
    <p>Stitching these individual projects together to make them feel like a unified experience isn’t as difficult as you might think: It only takes a few lines of CSS magic. What we <i>absolutely do not want</i> to happen is to leak our implementation details and internal decisions to our users. If we fail to make this user experience feel like one cohesive frontend, then we’ve done a grave injustice to our users. </p><p>To accomplish this sleight of hand, let us take a little trip in understanding how view transitions and document preloading come into play.</p>
    <div>
      <h3>View transitions</h3>
      <a href="#view-transitions">
        
      </a>
    </div>
    <p>When we want to seamlessly navigate between two distinct pages while making it feel smooth to the end user, <a href="https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API"><u>view transitions</u></a> are quite useful. Defining specific <a href="https://www.w3schools.com/jsref/dom_obj_all.asp"><u>DOM elements</u></a> on our page to stick around until the next page is visible, and defining how any changes are handled, make for quite the powerful quilt-stitching tool for multi-page applications.</p><p>There may be, however, instances where making the various vertical microfrontends feel different is more than acceptable. Perhaps our marketing website, documentation, and dashboard are each uniquely defined, for instance. A user would not expect all three of those to feel cohesive as you navigate between the three parts. But… if you decide to introduce vertical slices to an individual experience such as the dashboard (e.g. <code>/dash/product-a</code> &amp; <code>/dash/product-b</code>), then users should <b>never</b> know they are two different repositories/workers/projects underneath.</p><p>Okay, enough talk — let’s get to work. I mentioned it was low-effort to make two separate projects feel as if they were one to a user, and if you have yet to hear about <a href="https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API"><u>CSS View Transitions</u></a> then I’m about to blow your mind.</p><p>What if I told you that you could make animated transitions between different views  — single-page app (SPA) or multi-page app (MPA) — feel as if they were one? Before any view transitions are added, if we navigate between pages owned by two different Workers, the interstitial loading state would be the white blank screen in our browser for some few hundred milliseconds until the full next page began rendering. Pages would not feel cohesive, and it certainly would not feel like a single-page application.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4vw1Am7gYUQPtmFFCRcsu1/774b881dff7ce1c26db88f30623dfc13/image3.png" />
          </figure><p><sup>Appears as multiple navigation elements between each site.</sup></p><p>If we want elements to stick around, rather than seeing a white blank page, we can achieve that by defining CSS View Transitions. With the code below, we’re telling our current document page that when a view transition event is about to happen, keep the <code>nav</code> DOM element on the screen, and if any delta in appearance exists between our existing page and our destination page, then we’ll animate that with an <code>ease-in-out</code> transition.</p><p>All of a sudden, two different Workers feel like one.</p>
            <pre><code>@supports (view-transition-name: none) {
  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation-duration: 0.3s;
    animation-timing-function: ease-in-out;
  }
  nav { view-transition-name: navigation; }
}</code></pre>
            
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4h6Eh5LSX4552QJDvV1l7o/a5a43ee0e6e011bca58ecc2d74902744/image1.png" />
          </figure><p><sup>Appears as a single navigation element between three distinct sites.</sup></p>
    <div>
      <h3>Preloading</h3>
      <a href="#preloading">
        
      </a>
    </div>
    <p>Transitioning between two pages makes it <i>look</i> seamless — and we also want it to <i>feel</i> as instant as a client-side SPA. While currently Firefox and Safari do not support <a href="https://developer.mozilla.org/en-US/docs/Web/API/Speculation_Rules_API"><u>Speculation Rules</u></a>, Chrome/Edge/Opera do support the more recent newcomer. The speculation rules API is designed to improve performance for future navigations, particularly for document URLs, making multi-page applications feel more like single-page applications.</p><p>Breaking it down into code, what we need to define is a script rule in a specific format that tells the supporting browsers how to prefetch the other vertical slices that are connected to our web application — likely linked through some shared navigation.</p>
            <pre><code>&lt;script type="speculationrules"&gt;
  {
    "prefetch": [
      {
        "urls": ["https://product-a.com", "https://product-b.com"],
        "requires": ["anonymous-client-ip-when-cross-origin"],
        "referrer_policy": "no-referrer"
      }
    ]
  }
&lt;/script&gt;</code></pre>
            <p>With that, our application prefetches our other microfrontends and holds them in our in-memory cache, so if we were to navigate to those pages it would feel nearly instant.</p><p>You likely won’t require this for clearly discernible vertical slices (marketing, docs, dashboard) because users would expect a slight load between them. However, it is highly encouraged to use when vertical slices are defined within a specific visible experience (e.g. within dashboard pages).</p><p>Between <a href="https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API"><u>View Transitions</u></a> and <a href="https://developer.mozilla.org/en-US/docs/Web/API/Speculation_Rules_API"><u>Speculation Rules</u></a>, we are able to tie together entirely different code repositories to feel as if they were served from a single-page application. Wild if you ask me.</p>
    <div>
      <h2>Zero-config request routing</h2>
      <a href="#zero-config-request-routing">
        
      </a>
    </div>
    <p>Now we need a mechanism to host multiple applications, and a method to stitch them together as requests stream in. Defining a single Cloudflare Worker as the “Router” allows a single logical point (at the edge) to handle network requests and then forward them to whichever vertical microfrontend is responsible for that URL path. Plus it doesn’t hurt that then we can map a single domain to that router Worker and the rest “just works.”</p>
    <div>
      <h3>Service bindings</h3>
      <a href="#service-bindings">
        
      </a>
    </div>
    <p>If you have yet to explore Cloudflare Worker <a href="https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/"><u>service bindings</u></a>, then it is worth taking a moment to do so. </p><p>Service bindings allow one Worker to call into another, without going through a publicly-accessible URL. A Service binding allows Worker A to call a method on Worker B, or to forward a request from Worker A to Worker. Breaking it down further, the Router Worker can call into each vertical microfrontend Worker that has been defined (e.g. marketing, docs, dashboard), assuming each of them were Cloudflare Workers.</p><p>Why is this important? This is precisely the mechanism that “stitches” these vertical slices together. We’ll dig into how the request routing is handling the traffic split in the next section. But to define each of these microfrontends, we’ll need to update our Router Worker’s wrangler definition, so it knows which frontends it’s allowed to call into.
</p>
            <pre><code>{
  "$schema": "./node_modules/wrangler/config-schema.json",
  "name": "router",
  "main": "./src/router.js",
  "services": [
    {
      "binding": "HOME",
      "service": "worker_marketing"
    },
    {
      "binding": "DOCS",
      "service": "worker_docs"
    },
    {
      "binding": "DASH",
      "service": "worker_dash"
    },
  ]
}</code></pre>
            <p>Our above sample definition is defined in our Router Worker, which then tells us that we are permitted to make requests into three separate additional Workers (marketing, docs, and dash). Granting permissions is as simple as that, but let’s tumble into some of the more complex logic with request routing and HTML rewriting network responses.</p>
    <div>
      <h3>Request routing</h3>
      <a href="#request-routing">
        
      </a>
    </div>
    <p>With knowledge of the various other Workers we are able to call into if needed, now we need some logic in place to know where to direct network requests when. Since the Router Worker is assigned to our custom domain, all incoming requests hit it first at the network edge. It then determines which Worker should handle the request and manages the resulting response. </p><p>The first step is to map URL paths to associated Workers. When a certain request URL is received, we need to know where it needs to be forwarded. We do this by defining rules. While we support wildcard routes, dynamic paths, and parameter constraints, we are going to stay focused on the basics — literal path prefixes — as it illustrates the point more clearly. </p><p> In this example, we have three microfrontends:</p>
            <pre><code>/      = Marketing
/docs  = Documentation
/dash  = Dashboard</code></pre>
            <p>Each of the above paths need to be mapped to an actual Worker (see our wrangler definition for services in the section above). For our Router Worker, we define an additional variable with the following data, so we can know which paths should map to which service bindings. We now know where to route users as requests come in! Define a wrangler variable with the name ROUTES and the following contents:</p>
            <pre><code>{
  "routes":[
    {"binding": "HOME", "path": "/"},
    {"binding": "DOCS", "path": "/docs"},
    {"binding": "DASH", "path": "/dash"}
  ]
}</code></pre>
            <p>Let’s envision a user visiting our website path <code>/docs/installation</code>. Under the hood, what happens is the request first reaches our Router Worker which is in charge of understanding what URL paths map to which individual Workers. It understands that the <code>/docs</code> path prefix is mapped to our <code>DOCS</code> service binding which referencing our wrangler file points us at our <code>worker_docs</code> project. Our Router Worker, knowing that <code>/docs</code> is defined as a vertical microfrontend route, removes the <code>/docs</code> prefix from the path and forwards the request to our <code>worker_docs</code> Worker to handle the request and then finally returns whatever response we get.</p><p>Why does it drop the <code>/docs</code> path, though? This was an implementation detail choice that was made so that when the Worker is accessed via the Router Worker, it can clean up the URL to handle the request <i>as if </i>it were called from outside our Router Worker. Like any Cloudflare Worker, our <code>worker_docs</code> service might have its own individual URL where it can be accessed. We decided we wanted that service URL to continue to work independently. When it’s attached to our new Router Worker, it would automatically handle removing the prefix, so the service could be accessible from its own defined URL or through our Router Worker… either place, doesn’t matter.</p>
    <div>
      <h3>HTMLRewriter</h3>
      <a href="#htmlrewriter">
        
      </a>
    </div>
    <p>Splitting our various frontend services with URL paths (e.g. <code>/docs</code> or <code>/dash</code>) makes it easy for us to forward a request, but when our response contains HTML that doesn’t know it’s being reverse proxied through a path component… well, that causes problems. </p><p>Say our documentation website has an image tag in the response <code>&lt;img src="./logo.png" /&gt;</code>. If our user was visiting this page at <code>https://website.com/docs/</code>, then loading the <code>logo.png</code> file would likely fail because our <code>/docs</code> path is somewhat artificially defined only by our Router Worker.</p><p>Only when our services are accessed through our Router Worker do we need to do some HTML rewriting of absolute paths so our returned browser response references valid assets. In practice what happens is that when a request passes through our Router Worker, we pass the request to the correct Service Binding, and we receive the response from that. Before we pass that back to the client, we have an opportunity to rewrite the DOM — so where we see absolute paths, we go ahead and prepend that with the proxied path. Where previously our HTML was returning our image tag with <code>&lt;img src="./logo.png" /&gt;</code> we now modify it before returning to the client browser to <code>&lt;img src="./docs/logo.png" /&gt;</code>.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/10jKx6qt2YcarpDyEsFNYV/3b0f11f56e3c9b2deef59934cf8efa7f/image2.png" />
          </figure><p>Let’s return for a moment to the magic of CSS view transitions and document preloading. We could of course manually place that code into our projects and have it work, but this Router Worker will <i>automatically</i> handle that logic for us by also using <a href="https://developers.cloudflare.com/workers/runtime-apis/html-rewriter/"><u>HTMLRewriter</u></a>. </p><p>In your Router Worker <code>ROUTES</code> variable, if you set <code>smoothTransitions</code> to <code>true</code> at the root level, then the CSS transition views code will be added automatically. Additionally, if you set the <code>preload</code> key within a route to <code>true</code>, then the script code speculation rules for that route will automatically be added as well. </p><p>Below is an example of both in action:</p>
            <pre><code>{
  "smoothTransitions":true, 
  "routes":[
    {"binding": "APP1", "path": "/app1", "preload": true},
    {"binding": "APP2", "path": "/app2", "preload": true}
  ]
}</code></pre>
            
    <div>
      <h2>Get started</h2>
      <a href="#get-started">
        
      </a>
    </div>
    <p>You can start building with the Vertical Microfrontend template today.</p><p>Visit the Cloudflare Dashboard <a href="https://dash.cloudflare.com/?to=/:account/workers-and-pages/create?type=vmfe"><u>deeplink here</u></a> or go to “Workers &amp; Pages” and click the “Create application” button to get started. From there, click “Select a template” and then “Create microfrontend” and you can begin configuring your setup.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1teTcTNHzQH3yCvbTz3xyU/8f9a4b2ef3ec1c6ed13cbdc51d6b13c5/image5.png" />
          </figure><p>
Check out the <a href="https://developers.cloudflare.com/workers/framework-guides/web-apps/microfrontends"><u>documentation</u></a> to see how to map your existing Workers and enable View Transitions. We can't wait to see what complex, multi-team applications you build on the edge!</p> ]]></content:encoded>
            <category><![CDATA[Cloudflare Workers]]></category>
            <category><![CDATA[Developer Platform]]></category>
            <category><![CDATA[Developers]]></category>
            <category><![CDATA[Dashboard]]></category>
            <category><![CDATA[Front End]]></category>
            <category><![CDATA[Micro-frontends]]></category>
            <guid isPermaLink="false">2u7SNZ4BZcQYHZYKqmdEaM</guid>
            <dc:creator>Brayden Wilmoth</dc:creator>
        </item>
        <item>
            <title><![CDATA[Your frontend, backend, and database — now in one Cloudflare Worker]]></title>
            <link>https://blog.cloudflare.com/full-stack-development-on-cloudflare-workers/</link>
            <pubDate>Tue, 08 Apr 2025 14:05:00 GMT</pubDate>
            <description><![CDATA[ You can now deploy static sites and full-stack applications on Cloudflare Workers. Framework support for React Router v7, Astro, Vue and more are generally available today and Cloudflare Vite plugin.  ]]></description>
            <content:encoded><![CDATA[ <p><a href="https://blog.cloudflare.com/builder-day-2024-announcements/#static-asset-hosting"><u>In September 2024</u></a>, we introduced beta support for <a href="https://www.cloudflare.com/developer-platform/solutions/hosting/">hosting</a>, storing, and serving <a href="https://developers.cloudflare.com/workers/static-assets/"><u>static assets</u></a> for free on <a href="https://www.cloudflare.com/developer-platform/products/workers/">Cloudflare Workers</a> — something that was previously only possible on <a href="https://blog.cloudflare.com/cloudflare-pages/"><u>Cloudflare Pages</u></a>. Being able to host these assets — your client-side JavaScript, HTML, CSS, fonts, and images — was a critical missing piece for developers looking to build a full-stack application within a <b>single Worker</b>. </p><p>Today we’re announcing ten big improvements to building apps on Cloudflare. All together, these new additions allow you to build and host projects ranging from simple static sites to full-stack applications, all on Cloudflare Workers:</p><ul><li><p>Cloudflare Workers now provides production ready, <b>generally available</b> (GA) support for <a href="https://developers.cloudflare.com/workers/frameworks/framework-guides/remix/"><u>React Router v7 (Remix)</u></a>, <a href="https://developers.cloudflare.com/workers/frameworks/framework-guides/astro/"><u>Astro</u></a>, <a href="https://developers.cloudflare.com/workers/frameworks/framework-guides/hono/"><u>Hono</u></a>, <a href="https://developers.cloudflare.com/workers/frameworks/framework-guides/vue/"><u>Vue.js</u></a>, <a href="https://developers.cloudflare.com/workers/frameworks/framework-guides/nuxt/"><u>Nuxt</u></a>, <a href="https://developers.cloudflare.com/workers/frameworks/framework-guides/svelte/"><u>Svelte (SvelteKit)</u></a>, and <a href="https://developers.cloudflare.com/workers/frameworks/"><u>more</u></a>, with GA support for more frameworks including <a href="https://developers.cloudflare.com/workers/frameworks/framework-guides/nextjs/"><u>Next.js</u></a>, <a href="https://developers.cloudflare.com/workers/frameworks/framework-guides/angular/"><u>Angular</u></a>, and <a href="https://developers.cloudflare.com/workers/frameworks/framework-guides/solid/"><u>SolidJS</u></a> (SolidStart) to follow in Q2 2025. </p></li><li><p>You can build complete full-stack apps on Workers without a framework: you can “<a href="https://blog.cloudflare.com/introducing-the-cloudflare-vite-plugin/"><u>just use Vite</u></a>" and React together, and build a backend API in the same Worker. See our <a href="https://github.com/cloudflare/templates/tree/staging/vite-react-template"><u>Vite + React template</u></a> for an example.</p></li><li><p>The adapter for Next.js — <a href="https://opennext.js.org/cloudflare"><u>@opennextjs/cloudflare</u></a>, introduced in September 2024 as an early alpha, <a href="https://blog.cloudflare.com/deploying-nextjs-apps-to-cloudflare-workers-with-the-opennext-adapter"><u>is now v1.0-beta</u></a>, and will be GA in the coming weeks. Those using the OpenNext adapter will also be able to easily upgrade to the <a href="https://github.com/vercel/next.js/discussions/77740"><u>recently announced Next.js Deployments API</u></a>. </p></li><li><p>The <a href="https://blog.cloudflare.com/introducing-the-cloudflare-vite-plugin"><u>Cloudflare Vite plugin</u></a> is now v1.0 and generally available. The Vite plugin allows you to run Vite’s development server in the Workers runtime (<code>workerd</code>), meaning you get all the benefits of Vite, including <a href="https://vite.dev/guide/features.html#hot-module-replacement"><u>Hot Module Replacement</u></a>, while still being able to use features that are exclusive to Workers (like Durable Objects).</p></li><li><p>You can now use static <a href="https://developers.cloudflare.com/workers/static-assets/headers/"><u>_headers</u></a> and <a href="https://developers.cloudflare.com/workers/static-assets/redirects/"><u>_redirects</u></a> configuration files for your applications on Workers, something that was previously only available on Pages. These files allow you to add simple headers and configure redirects without executing any Worker code. </p></li><li><p>In addition to <a href="https://developers.cloudflare.com/hyperdrive/configuration/connect-to-postgres/"><u>PostgreSQL</u></a>, you can now connect to <a href="https://blog.cloudflare.com/building-global-mysql-apps-with-cloudflare-workers-and-hyperdrive"><u>MySQL databases in addition from Cloudflare Workers, via Hyperdrive</u></a>. Bring your existing Planetscale, AWS, GCP, Azure, or other MySQL database, and Hyperdrive will take care of pooling connections to your database and eliminating unnecessary roundtrips by caching queries.</p></li><li><p><a href="#node-js-compatibility"><u>More Node.js APIs are available</u></a> in the Workers Runtime — including APIs from the <code>crypto</code>, <code>tls</code>, <code>net</code>, and <code>dns </code>modules. We’ve also increased the maximum CPU time for a Workers request from 30 seconds to 5 minutes.</p></li><li><p>You can now <a href="https://blog.cloudflare.com/deploy-workers-applications-in-seconds"><u>bring any repository from GitHub or GitLab that contains a Worker application</u></a>, and <a href="https://developers.cloudflare.com/workers/ci-cd/builds/"><u>Workers Builds</u></a> will take care of deploying the app as a new Worker on your account. <a href="#workers-builds"><u>Workers Builds is also starting much more quickly</u></a> (by up to 6 seconds for every build). </p></li><li><p>You can now set up Workers Builds to <a href="https://developers.cloudflare.com/workers/ci-cd/builds/build-branches/#configure-non-production-branch-builds"><u>run on non-production branches</u></a>, and preview URLs will be <a href="https://developers.cloudflare.com/workers/ci-cd/builds/git-integration/github-integration/#pull-request-comment"><u>posted back to GitHub as a comment</u></a>. </p></li><li><p>The <a href="https://developers.cloudflare.com/images/transform-images/bindings/"><u>Images binding in Workers</u></a> is generally available, allowing you to build more flexible, programmatic workflows. </p></li></ul><p>These improvements allow you to build both simple static sites and more complex server-side rendered applications. Like <a href="https://www.cloudflare.com/developer-platform/products/pages/">Pages</a>, you only get charged when your Worker code runs, meaning you can host and serve static sites for free. When you want to do any rendering on the server or need to build an API, simply add a Worker to handle your backend. And when you need to read or write data in your app, you can connect to an existing database with <a href="https://developers.cloudflare.com/hyperdrive/"><u>Hyperdrive</u></a>, or use any of our storage solutions: <a href="https://developers.cloudflare.com/kv/"><u>Workers KV</u></a>, <a href="https://developers.cloudflare.com/r2/"><u>R2</u></a>, <a href="https://developers.cloudflare.com/durable-objects/"><u>Durable Objects</u></a>, or <a href="https://developers.cloudflare.com/d1/"><u>D1</u></a>. </p><p>If you'd like to dive straight into code, you can deploy a single-page application built with Vite and React, with the option to connect to a hosted database with Hyperdrive, by clicking this “Deploy to Cloudflare” button: </p><a href="https://deploy.workers.cloudflare.com/?url=https://github.com/cloudflare/templates/tree/main/react-postgres-fullstack-template"><img src="https://deploy.workers.cloudflare.com/button" /></a>
<p></p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/56hrgUpOebvOdzbI8j6liw/bc9e7d01dde8cb6a8a4623aec3abc883/1.jpg" />
          </figure>
    <div>
      <h2>Start with Workers</h2>
      <a href="#start-with-workers">
        
      </a>
    </div>
    <p>Previously, you needed to choose between building on Cloudflare Pages or Workers (or use Pages for one part of your app, and Workers for another) just to get started. This meant figuring out what your app needed from the start, and hoping that if your project evolved, you wouldn’t be stuck with the wrong platform and architecture. Workers was designed to be a flexible platform, allowing developers to evolve projects as needed — and so, we’ve <a href="https://blog.cloudflare.com/pages-and-workers-are-converging-into-one-experience/"><u>worked to bring pieces of Pages into Workers</u></a> over the years.  </p><p>Now that Workers supports both serving static assets <b>and </b>server-side rendering, you should <b>start with Workers</b>. Cloudflare Pages will continue to be supported, but, going forward, all of our investment, optimizations, and feature work will be dedicated to improving Workers. We aim to make Workers the best platform for building full-stack apps, building upon your feedback of what went well with Pages and what we could improve. </p><p>Before, building an app on Pages meant you got a really easy, opinionated on-ramp, but you’d eventually hit a wall if your application got more complex. If you wanted to use Durable Objects to manage state, you would need to set up an entirely separate Worker to do so, ending up with a complicated deployment and more overhead. You also were limited to real-time logs, and could only roll out changes all in one go. </p><p>When you build on Workers, you can immediately bind to any other Developer Platform service (including <a href="https://developers.cloudflare.com/durable-objects/"><u>Durable Objects</u></a>, <a href="https://developers.cloudflare.com/email-routing/email-workers/"><u>Email Workers</u></a>, and more), and manage both your front end and back end in a single project — all with a single deployment. You also get the whole suite of <a href="https://developers.cloudflare.com/workers/observability/"><u>Workers observability</u></a> tooling built into the platform, such as <a href="https://developers.cloudflare.com/workers/observability/logs/workers-logs/"><u>Workers Logs</u></a>. And if you want to rollout changes to only a certain percentage of traffic, you can do so with <a href="https://developers.cloudflare.com/workers/configuration/versions-and-deployments/gradual-deployments/"><u>Gradual Deployments</u></a>.  </p><p>These latest improvements are part of our goal to bring the best parts of Pages into Workers. For example, we now support static  <a href="https://developers.cloudflare.com/workers/static-assets/headers/"><u>_headers</u></a> and <a href="https://developers.cloudflare.com/workers/static-assets/redirects/"><u>_redirects</u></a> config files, so that you can easily take an existing project from Pages (or another platform) and move it over to Workers, without needing to change your project. We also directly integrate with GitHub and GitLab with <a href="https://developers.cloudflare.com/workers/ci-cd/builds/"><u>Workers Builds</u></a>, providing automatic builds and deployments. And starting today, <a href="https://developers.cloudflare.com/workers/configuration/previews/"><u>Preview URLs</u></a> are <a href="https://developers.cloudflare.com/workers/ci-cd/builds/git-integration/github-integration/#pull-request-comment"><u>posted back to your repository as a comment</u></a>, with feature branch aliases and environments coming soon. </p><p>To learn how to migrate an existing project from Pages to Workers, read our <a href="https://developers.cloudflare.com/workers/static-assets/migrate-from-pages/"><u>migration guide</u></a>. </p><p>Next, let’s talk about how you can build applications with different rendering modes on Workers.  </p>
    <div>
      <h2>Building static sites, SPAs, and SSR on Workers</h2>
      <a href="#building-static-sites-spas-and-ssr-on-workers">
        
      </a>
    </div>
    <p>As a quick primer, here are all the architectures and rendering modes we’ll be discussing that are supported on Workers: </p><ul><li><p><b>Static sites</b>: When you visit a static site, the server immediately returns pre-built static assets — HTML, CSS, JavaScript, images, and fonts. There’s no dynamic rendering happening on the server at request-time. Static assets are typically generated at build-time and served directly from a <a href="https://www.cloudflare.com/learning/cdn/what-is-a-cdn/"><u>CDN</u></a>, making static sites fast and easily cacheable. This approach works well for sites with content that rarely changes. </p></li><li><p><b>Single-Page Applications (SPAs)</b>:  When you load an SPA, the server initially sends a minimal HTML shell and a JavaScript bundle (served as static assets). Your browser downloads this JavaScript, which then takes over to render the entire user interface client-side. After the initial load, all navigation occurs without full-page refreshes, typically via client-side routing. This creates a fast, app-like experience. </p></li><li><p><b>Server-Side Rendered (SSR) applications</b>: When you first visit a site that uses SSR, the server generates a fully-rendered HTML page on-demand for that request. Your browser immediately displays this complete HTML, resulting in a fast first page load. Once loaded, JavaScript "<a href="https://en.wikipedia.org/wiki/Hydration_(web_development)"><u>hydrates</u></a>" the page, adding interactivity. Subsequent navigations can either trigger new server-rendered pages or, in many modern frameworks, transition into client-side rendering similar to an SPA.</p></li></ul><p>Next, we’ll dive into how you can build these kinds of applications on Workers, starting with setting up your development environment. </p>
    <div>
      <h3>Setup: build and dev</h3>
      <a href="#setup-build-and-dev">
        
      </a>
    </div>
    <p>Before uploading your application, you need to bundle all of your client-side code into a directory of <b>static assets</b>. Wrangler bundles and builds your code when you run <code>wrangler dev</code>, but we also now support Vite with our <a href="https://www.npmjs.com/package/@cloudflare/vite-plugin"><u>new Vite plugin</u></a>. This is a great option for those already using Vite’s build tooling and development server — you can continue developing (and testing with <a href="https://developers.cloudflare.com/workers/testing/vitest-integration/"><u>Vitest</u></a>) using Vite’s development server, all using the Workers runtime. </p><p>To get started using the Cloudflare Vite plugin, you can scaffold a React application using Vite and our plugin, by running: </p>
            <pre><code>npm create cloudflare@latest my-react-app -- --framework=react</code></pre>
            <p>When you open the project, you should see a directory structure like this: </p>
            <pre><code>...
├── api
│   └── index.ts
├── public
│   └── ...
├── src
│   └── ...
...
├── index.html
├── package.json
├── vite.config.ts
└── wrangler.jsonc</code></pre>
            <p>If you run <code>npm run build</code>, you’ll see a new folder appear, named <code>/dist</code>. </p>
            <pre><code>...
├── api
│   └── index.ts
├── dist
│   └── ...
├── public
│   └── ...
├── src
│   └── ...
...
├── index.html
├── package.json
├── vite.config.ts
└── wrangler.jsonc</code></pre>
            <p>The Vite plugin informs Wrangler that this <code>/dist</code> directory contains the project’s built static assets — which, in this case, includes client-side code, some CSS files, and images. </p><p>Once deployed, this single-page application (SPA) architecture will look something like this: </p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6syBGxC8udJi8wlsqvLTXW/526d05095a953cb6d29526abfd4f4b3a/2.jpg" />
          </figure><p>When a request comes in, Cloudflare looks at the pathname and automatically serves any static assets that match that pathname. For example, if your static assets directory includes a <code>blog.html</code> file, requests for <code>example.com/blog</code> get that file. </p>
    <div>
      <h3>Static sites</h3>
      <a href="#static-sites">
        
      </a>
    </div>
    <p>If you have a static site created by a static site generator (SSG) like <a href="https://docs.astro.build/en/concepts/why-astro/"><u>Astro</u></a>, all you need to do is create a <code>wrangler.jsonc</code> file (or <code>wrangler.toml</code>) and tell Cloudflare where to find your built assets: </p>
            <pre><code>// wrangler.jsonc 

{
  "name": "my-static-site",
  "compatibility_date": "2025-04-01",
  "assets": {
    "directory": "./dist",
  }
}</code></pre>
            <p>Once you’ve added this configuration, you can simply build your project and run wrangler deploy.  Your entire site will then be uploaded and ready for traffic on Workers. Once deployed and requests start flowing in, your static site will be <a href="https://developers.cloudflare.com/workers/static-assets/#caching-behavior"><u>cached across Cloudflare’s network</u></a>.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/OaFnoUfvhzfjwv537fh5S/763524c2ed3b2beb61304576723667ea/3.jpg" />
          </figure><p>You can try starting a fresh Astro project on Workers today by running:</p>
            <pre><code>npm create cloudflare@latest my-astro-app -- --framework=astro</code></pre>
            <p>You can see our other supported Frameworks and how to get started in our <a href="https://developers.cloudflare.com/workers/frameworks/"><u>framework guides</u></a>. </p>
    <div>
      <h3>Single-page applications (SPAs) </h3>
      <a href="#single-page-applications-spas">
        
      </a>
    </div>
    <p>If you have a single-page application, you can explicitly enable <code>single-page-application</code> mode in your Wrangler configuration: </p>
            <pre><code>{
 "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"
 }
}</code></pre>
            <p>By enabling this, the platform assumes that any navigation request (requests which include a <code>Sec-Fetch-Mode: navigate</code> header) are intended for static assets and will serve up <code>index.html</code> whenever a matching static asset match cannot be found. For non-navigation requests (such as requests for data) that don't match a static asset, Cloudflare will invoke the Worker script. With this setup, you can render the frontend with React, use a Worker to handle back-end operations, and use Vite to help stitch the two together. This is a great option for porting over older SPAs built with <code>create-react-app</code>, <a href="https://react.dev/blog/2025/02/14/sunsetting-create-react-app"><u>which was recently sunset</u></a>. </p><p>Another thing to note in this Wrangler configuration file: we’ve defined a Hyperdrive binding and enabled <a href="https://developers.cloudflare.com/workers/configuration/smart-placement/"><u>Smart Placement</u></a>. Hyperdrive lets us use an existing database<i> and</i> handles connection pooling. This solves a long-standing challenge of connecting Workers (which run in a highly distributed, serverless environment) directly to traditional databases. By design, Workers operate in lightweight V8 isolates with no persistent TCP sockets and a strict CPU/memory limit. This isolation is great for security and speed, but it makes it difficult to hold open database connections. Hyperdrive addresses these constraints by acting as a “bridge” between Cloudflare’s network and your database, taking care of the heavy lifting of maintaining stable connections or pools so that Workers can reuse them.  By turning on Smart Placement, we also ensure that if requests to our Worker originate far from the database (causing latency), Cloudflare can choose to relocate both the Worker—which handles the database connection—and the Hyperdrive “bridge” to a location closer to the database, ​​reducing round-trip times. </p>
    <div>
      <h4>SPA example: Worker code</h4>
      <a href="#spa-example-worker-code">
        
      </a>
    </div>
    <p>Let’s look at the <a href="https://github.com/korinne/example-spa-worker"><u>“Deploy to Cloudflare” example</u></a> at the top of this blog. In <code>api/index.js</code>, we’ve defined an API (using Hono) which connects to a hosted database through Hyperdrive. </p>
            <pre><code>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) =&gt; {
 // 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,
};</code></pre>
            <p>When deployed, our app’s architecture looks something like this: </p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/56hrgUpOebvOdzbI8j6liw/bc9e7d01dde8cb6a8a4623aec3abc883/1.jpg" />
          </figure><p>If Smart Placement moves the placement of my Worker to run closer to my database, it could look like this: </p><div>
  
</div>
<p></p>
    <div>
      <h3>Server-Side Rendering (SSR)</h3>
      <a href="#server-side-rendering-ssr">
        
      </a>
    </div>
    <p>If you want to handle rendering on the server, we support a number of popular full-stack <a href="https://developers.cloudflare.com/workers/frameworks/"><u>frameworks</u></a>. </p><p>Here’s a version of our previous example, now using React Router v7’s server-side rendering:</p><a href="https://deploy.workers.cloudflare.com/?url=https://github.com/cloudflare/templates/tree/main/react-router-postgres-ssr-template"><img src="https://deploy.workers.cloudflare.com/button" /></a>
<p></p><p>You could also use Next.js with the <a href="https://opennext.js.org/cloudflare"><u>OpenNext adapter</u></a>, or any other <a href="https://developers.cloudflare.com/workers/frameworks/"><u>framework listed in our framework guides</u></a>. </p>
    <div>
      <h2>Deploy to Workers, with as few changes as possible</h2>
      <a href="#deploy-to-workers-with-as-few-changes-as-possible">
        
      </a>
    </div>
    
    <div>
      <h3>Node.js compatibility</h3>
      <a href="#node-js-compatibility">
        
      </a>
    </div>
    <p>We’ve also continued to make progress supporting Node.js APIs, recently adding support for the <code>crypto</code>, <code>tls</code>, <code>net</code>, and <code>dns</code> modules. This allows existing applications and libraries that rely on these Node.js modules to run on Workers. Let’s take a look at an example:</p><p>Previously, if you tried to use the <code>mongodb</code> package, you encountered the following error:</p>
            <pre><code>Error: [unenv] dns.resolveTxt is not implemented yet!</code></pre>
            <p>This occurred when <code>mongodb</code> used the <code>node:dns</code> module to do a DNS lookup of a hostname. Even if you avoided that issue, you would have encountered another error when <code>mongodb</code> tried to use <code>node:tls</code> to securely connect to a database.</p><p>Now, you can use <code>mongodb</code> as expected because <code>node:dns</code> and <code>node:tls</code> are supported. The same can be said for libraries relying on <code>node:crypto</code> and <code>node:net</code>.</p><p>Additionally, Workers <a href="https://developers.cloudflare.com/changelog/2025-03-11-process-env-support/"><u>now expose environment variables and secrets on the process.env object</u></a> when the <code>nodejs_compat</code> compatibility flag is on and the compatibility date is set to <code>2025-04-01</code> or beyond. Some libraries (and developers) assume that this object will be populated with variables, and rely on it for top-level configuration. Without the tweak, libraries may have previously broken unexpectedly and developers had to write additional logic to handle variables on Cloudflare Workers.</p><p>Now, you can just access your variables as you would in Node.js.</p>
            <pre><code>const LOG_LEVEL = process.env.LOG_LEVEL || "info";</code></pre>
            
    <div>
      <h3>Additional Worker CPU time</h3>
      <a href="#additional-worker-cpu-time">
        
      </a>
    </div>
    <p>We have also <a href="https://developers.cloudflare.com/changelog/2025-03-25-higher-cpu-limits/"><u>raised the maximum CPU time per Worker request</u></a> from 30 seconds to 5 minutes. This allows for compute-intensive operations to run for longer without timing out. Say you want to use the newly supported <code>node:crypto</code> module to hash a very large file, you can now do this on Workers without having to rely on external compute for CPU-intensive operations.</p>
    <div>
      <h3>Workers Builds </h3>
      <a href="#workers-builds">
        
      </a>
    </div>
    <p>We’ve also made improvements to <a href="https://developers.cloudflare.com/workers/ci-cd/builds/"><u>Workers Builds</u></a>, which allows you to connect a Git repository to your Worker, so that you can have automatic builds and deployments on every pushed change. Workers Builds was introduced during <a href="https://blog.cloudflare.com/builder-day-2024-announcements/#continuous-integration-and-delivery"><u>Builder Day 2024</u></a>, and initially only allowed you to connect a repository to an existing Worker. Now, you can bring a repository and <a href="https://blog.cloudflare.com/deploy-workers-applications-in-seconds/"><u>immediately deploy it as a new Worker</u></a>, reducing the amount of setup and button clicking needed to bring a project over. We’ve improved the performance of Workers Builds by reducing the latency of build starts by <b>6 seconds</b> — they now start within <b>10 seconds</b> on average. We also boosted API responsiveness, achieving a <b>7x </b>latency improvement thanks to Smart Placement. </p><ul><li><p><b>Note</b>: On April 2, 2025, Workers Builds transitioned to a new pricing model, as announced during <a href="https://blog.cloudflare.com/builder-day-2024-announcements/"><u>Builder Day 2024</u></a>. Free plan users are now capped at 3,000 minutes of build time, and Workers Paid subscription users will have a new usage-based model with 6,000 free minutes included and $0.005 per build minute pricing after. To better support concurrent builds, Paid plans will also now get six (6) concurrent builds, making it easier to work across multiple projects and monorepos. For more information on pricing, see the <a href="https://developers.cloudflare.com/workers/ci-cd/builds/limits-and-pricing/"><u>documentation</u></a>.</p></li></ul><p>You can also set up Workers Builds to <a href="https://developers.cloudflare.com/workers/ci-cd/builds/build-branches/#configure-non-production-branch-builds"><u>run on non-production branches</u></a>, and preview URLs will be <a href="https://developers.cloudflare.com/workers/ci-cd/builds/git-integration/github-integration/#pull-request-comment"><u>posted back to GitHub as a comment</u></a>. </p>
    <div>
      <h3>Bind the Images API to your Worker</h3>
      <a href="#bind-the-images-api-to-your-worker">
        
      </a>
    </div>
    <p>Last week, we wrote a <a href="https://blog.cloudflare.com/improve-your-media-pipelines-with-the-images-binding-for-cloudflare-workers/"><u>blog post</u></a> that covers how the Images binding enables more flexible, programmatic workflows for image optimization.</p><p>Previously, you could access image optimization features by calling <code>fetch()</code> in your Worker. This method requires the original image to be retrievable by URL. However, you may have cases where images aren’t accessible from a URL, like when you want to compress user-uploaded images before they are uploaded to your storage. With the Images binding, you can directly optimize an image by operating on its body as a stream of bytes.</p><p>To learn more, read our guide on <a href="https://developers.cloudflare.com/images/tutorials/optimize-user-uploaded-image"><u>transforming an image before it gets uploaded to R2</u></a>.</p>
    <div>
      <h2>Start building today</h2>
      <a href="#start-building-today">
        
      </a>
    </div>
    <p>We’re excited to see what you’ll build, and are focused on new features and improvements to make it  easier to create any application on Workers. Much of this work was made even better by community feedback, and we encourage everyone to <a href="https://discord.com/invite/cloudflaredev"><u>join our Discord</u></a> to participate in the discussion. </p><p><b>Helpful resources to get you started:</b></p><ul><li><p><a href="https://developers.cloudflare.com/workers/frameworks/"><u>Framework guides</u></a> </p></li><li><p><a href="https://developers.cloudflare.com/workers/static-assets/migrate-from-pages/"><u>Migration guide</u></a> </p></li><li><p><a href="https://developers.cloudflare.com/workers/static-assets/"><u>Static assets documentation</u></a> </p></li><li><p><a href="https://developers.cloudflare.com/workers/vite-plugin"><u>Cloudflare Vite plugin documentation</u></a></p></li></ul><p></p> ]]></content:encoded>
            <category><![CDATA[Developer Week]]></category>
            <category><![CDATA[Developers]]></category>
            <category><![CDATA[Front End]]></category>
            <category><![CDATA[Full Stack]]></category>
            <category><![CDATA[General Availability]]></category>
            <category><![CDATA[Cloudflare Pages]]></category>
            <category><![CDATA[Cloudflare Workers]]></category>
            <category><![CDATA[MySQL]]></category>
            <category><![CDATA[Hyperdrive]]></category>
            <guid isPermaLink="false">67CgcpMED2Rw0BozjKbdUz</guid>
            <dc:creator>Korinne Alpers</dc:creator>
        </item>
    </channel>
</rss>