Vertical microfrontends arrive on Workers

Cloudflare has released a new Worker template for Vertical Microfrontends (VMFE). The template maps multiple independent Workers to a single domain, so separate teams can own distinct URL paths — such as marketing, docs, or dashboard sections — and deploy them as fully independent projects. Users still see a single application because routing happens at the edge by path.

Most microfrontend setups are horizontal: different parts of one page come from different services. Vertical microfrontends invert that model by splitting the application on URL boundaries. A team that owns the /blog path controls everything for that route — framework, libraries, CI/CD, and deployment. That kind of full-stack ownership lets teams ship without waiting on or being blocked by other groups’ changes.

The approach also solves a common scaling problem. When a marketing site would be better built with Astro and a dashboard with React, teams shouldn’t be forced into one framework. Likewise, in a monolithic codebase where many teams ship together, one team’s regression can roll back everyone’s work. Vertical microfrontends keep the implementation details hidden while giving each team autonomy over its slice.

Defining slices by path

A vertical microfrontend is an architectural pattern where a single team owns an entire slice of the application’s functionality, from the user interface down to the CI/CD pipeline. Slices are defined by paths on a domain, and individual Workers can be associated with specific paths:

/      = Marketing
/docs  = Documentation
/blog  = Blog
/dash  = Dashboard

This can be taken further with more granular sub-path associations. Within a dashboard, different products are often segmented by URL depth, e.g. /dash/product-a. Navigating between two products could mean switching between two entirely different codebases:

/dash/product-a  = WorkerA
/dash/product-b  = WorkerB

Each of those paths is its own frontend project with zero shared code. The product-a and product-b routes map to separately deployed applications, each with its own frameworks, libraries, and CI/CD defined and owned by its own team.

Cloudflare experiences this pain directly: the dashboard has many individual teams owning their own products, and changes outside a team’s control can impact how users experience its product. Internally, Cloudflare now uses a similar strategy for its dashboard. When users navigate from the core dashboard into ZeroTrust, they are actually being routed to an entirely separate project by its path /:accountId/one.

Making separate projects feel unified

Stitching these projects together into a coherent user experience takes only a few lines of CSS — but the goal is to avoid leaking implementation details. If users can tell they’ve left one app and entered another, the abstraction has failed. Two browser APIs make the stitching work: view transitions and speculation rules.

View transitions

When navigating between two distinct pages, view transitions keep the experience smooth. By defining specific DOM elements to persist until the next page is visible, and by declaring how changes are handled, multi-page applications can feel like a single-page app.

The need for this varies by use case. For clearly separate experiences — a marketing site, documentation, and a dashboard — a user does not expect all three to feel identical as they navigate between them. But if vertical slices exist within one experience, such as the dashboard (e.g. /dash/product-a and /dash/product-b), users should never know they are tapping into two different repositories, Workers, or projects underneath.

Without any view transitions, navigating between pages owned by two different Workers results in an interstitial white blank screen for a few hundred milliseconds while the next page begins rendering. That breaks any illusion of cohesion.

BLOG-3105 image 1

Appears as multiple navigation elements between each site.

To keep elements on screen instead of showing a blank page, CSS view transitions tell the current document that when a transition event is about to happen, the nav DOM element should stay visible. If any appearance delta exists between the existing page and the destination page, it is animated with an ease-in-out transition. Two different Workers suddenly feel like one.

@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; }
}
BLOG-3015 3

Appears as a single navigation element between three distinct sites.

Preloading

Transitions make the switch look seamless, but the navigation itself should also feel immediate. The Speculation Rules API, supported in Chrome, Edge, and Opera (though not yet Firefox or Safari), prefetches document URLs for future navigations. It makes multi-page applications respond more like SPAs.

Defining a speculation rule involves a script in a specific format that tells supporting browsers which other vertical slices to prefetch — likely those referenced through shared navigation. Once these pages are held in the in-memory cache, navigating to them feels nearly instant.

<script type="speculationrules">
  {
    "prefetch": [
      {
        "urls": ["https://product-a.com", "https://product-b.com"],
        "requires": ["anonymous-client-ip-when-cross-origin"],
        "referrer_policy": "no-referrer"
      }
    ]
  }
</script>

Prefetching isn’t critical for clearly discernible vertical slices like marketing, docs, or a dashboard, where users expect a slight load between them. It is, however, highly recommended when vertical slices exist within a specific visible experience, such as between dashboard pages. Combined, view transitions and speculation rules turn separate code repositories into what users perceive as a single, seamless application. The template is available now in the Cloudflare dashboard.

Stitching Services Together at the Edge

Once each vertical slice is its own Worker, you need a way to route incoming traffic to the right one. A dedicated “Router” Worker can act as the single entry point: it accepts every request for a domain, inspects the URL, and forwards to the correct microfrontend.

Calling Workers Without Public URLs

Cloudflare’s service bindings let one Worker invoke another without exposing a public URL. The Router Worker defines bindings to each vertical microfrontend Worker it may need to reach, e.g. marketing, docs, or dash. Just declaring these in the Router’s wrangler config is enough to allow cross-Worker calls.

{
  "$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"
    },
  ]
}

Those three bindings in the sample config mean your Router can forward requests to worker_marketing, worker_docs, and worker_dash. The complexity sits in the routing logic and in DOM rewriting of the responses.

Mapping Paths to Worker Bindings

The Router listens on the custom domain and needs rules to decide which Worker handles what. Path-prefix mapping is the basic pattern: you define which prefixes belong to which service. For the three microfrontends in our wrangler config, we can model a routing table like this:

/      = Marketing
/docs  = Documentation
/dash  = Dashboard

An environment variable named ROUTES can store that mapping. The Router checks the first segment of the path, matches it to a key, and forwards the request along.

{
  "routes":[
    {"binding": "HOME", "path": "/"},
    {"binding": "DOCS", "path": "/docs"},
    {"binding": "DASH", "path": "/dash"}
  ]
}

Suppose a user lands on /docs/installation. The Router sees the /docs prefix, looks up the corresponding binding DOCS, strips the prefix, and forwards the call to worker_docs. That Worker then responds as if it had received the request directly at its own URL.

Removing the prefix before forwarding is a deliberate choice: it means worker_docs remains fully functional when accessed via its own standalone URL. The prefix stripping happens only in the Router, not inside the microfrontend.

Rewriting HTML for Proxied Responses

Forwarding requests across bindings leaves one problem: the HTML itself may contain absolute asset paths. If your docs site returns <img src="./logo.png" /> for a page shown at https://website.com/docs/, that asset resolves relative to the /docs/ directory. Without intervention, the browser would 404.

The Router applies HTMLRewriter to responses before returning them. Wherever it sees path-based asset references, it prepends the correct proxied path — turning ./logo.png into ./docs/logo.png for content served under /docs.

image2

That same rewriting layer is useful for adding navigation enhancements automatically. Two options can be toggled from the ROUTES config without touching the frontend code itself: setting smoothTransitions to true injects the CSS view-transition code; a route key preload set to true injects speculation-rule preloading scripts.

{
  "smoothTransitions":true, 
  "routes":[
    {"binding": "APP1", "path": "/app1", "preload": true},
    {"binding": "APP2", "path": "/app2", "preload": true}
  ]
}

Starting With the Template

A prebuilt Vertical Microfrontend template is available directly from the Cloudflare Dashboard. Use the deeplink, head to “Workers & Pages,” choose “Create application,” then “Select a template,” and pick “Create microfrontend” to start configuring the architecture.

image5

The documentation covers mapping existing Worker projects and enabling View Transitions. With the Router pattern and HTMLRewriter in place, you get a cohesive user experience across independently shipped workers — all resolved at the edge.