Why micro-frontends are moving to the edge

Enterprise web applications are enormous. Banking, e-commerce, travel, and insurance portals routinely span millions of lines of JavaScript, built by multiple teams collaborating in a single codebase. That arrangement typically yields low Lighthouse scores, sluggish startups, and a development environment where one team's mistake becomes everyone's problem.

Micro-frontends — the practice of splitting a frontend into self-contained mini-applications that teams own and deploy independently — have become a standard answer to those issues. Each micro-frontend renders one fragment of the page; the application stitches the fragments together so the user perceives a single, cohesive product. Fragments may be vertical features (checkout, account management) or horizontal ones (header, navigation).

The problem is the approach most teams use to combine those fragments. Client-side module federation requires the container application to boot before children can be fetched, which creates deep waterfall delays. Shared code must be duplicated or published as a library, and libraries cannot be tree-shaken or updated without coordination across all consuming teams. Server-side rendering with client-side micro-frontends helps, but it complicates operations and still introduces a hydration gap before controls become interactive.

Cloudflare Workers offers a different setup. Because Workers run in over 275 locations and communicate with near-zero overhead via service bindings, it is possible to implement fragments that render server-side and stream responses in parallel.

A fragments architecture on Cloudflare Workers

In this architecture, the browser requests a “root” Worker. That Worker calls child fragments, each hosted and rendered by its own Cloudflare Worker. Child fragments execute in parallel, producing HTML that is streamed to the parent, which pipes everything into a single response to the browser. Every fragment serves its own client-side assets — JavaScript, CSS, and images — through the response stream, so the browser can render gradually without waiting for the whole tree to finish.

Encapsulated, independently deployable teams

Fragments are fully self-contained. Redeploying a single Worker updates that fragment on the next request; no other fragment needs to be touched. Since fragments only render to HTML, server-only code — including security-sensitive logic — never reaches the browser or other fragments. Feature flags and gradual rollout can be handled safely inside a fragment without affecting the rest of the page.

Composability and caching

Because fragments are composable, any fragment can contain other fragments — a tree can be arbitrarily deep. That structure is valuable for large engineering organizations that need fine-grained division of labor and independent scaling. Expensive fragments can be cached separately, which gives you control over how much compute is spent assembling any given page.

Streaming performance

Each fragment issues requests to its children in parallel and pipes the resulting HTML into a single output stream. That keeps the time to first byte low for each piece of the page and lets the root fragment complete before any slow child holds things up. The result is strong Lighthouse scores and no hydration delay between first paint and first interaction.

Eager interactivity

Fragments can become interactive as soon as their HTML arrives — no need to wait for the full page to stream. The demo includes a header slider that simulates a network or database delay on the gallery stream. Even with the gallery still loading, the type-ahead filter is immediately usable. That can save users with unreliable connections from a lot of waiting.

The demo application, Cloud Gallery, is live at cloud-gallery.web-experiments.workers.dev. It consists of six cooperating Workers: a root main fragment, a header with the delay slider, a body fragment containing the filter and gallery fragments, plus a footer. Full source is available on GitHub.

Each fragment is a server-side rendered Qwik application deployed as its own Worker. You can navigate directly to a fragment — for example, the header is at cloud-gallery-header.web-experiments.workers.dev. A fragment is just a component rendered in a fetch() handler:

export default {
  fetch(request: Request, env: Record<string, unknown>): Promise<Response> {
    return renderResponse(request, env, <Header />, manifest, "header");
  },
};

The renderResponse() helper server-side renders the fragment and streams it into the body of a returned Response. Qwik is a natural fit because of its HTML-first nature and minimal JavaScript footprint. Assets are uploaded with Wrangler and served from Cloudflare’s network.

Assembling fragments at request time

Composing a page from nested fragments adds two responsibilities to the parent: it must fetch and stream each child's HTML into its own response, and it must forward asset requests to whichever child owns them.

Child placement is declared in the parent's JSX with a FragmentPlaceholder helper. The Cloud Gallery's “body” fragment, for example, renders placeholders for the “filter” and “gallery” fragments. When a placeholder renders, it issues a request for that child fragment and pipes the returned stream directly into the parent's output.

Asset routing follows a path convention: any asset hosted by a child is prefixed with /_fragment/<fragment-name>. A logo served by the header fragment lives at /_fragment/header/cf-logo.png. A parent adds the tryGetFragmentAsset() helper to its fetch() handler. The helper inspects the URL, and when the path matches the convention, it forwards the call to the correct child service.

The convention also fixes how HTML refers to those assets. The FragmentPlaceholder component sends a base search parameter with its child request to communicate the required prefix. On the child side, the renderResponse() helper pulls that parameter out and passes it to the renderer, ensuring any client-side JavaScript URLs are generated with the prefix. Components that need the value call the useFragmentRoot() hook, which reads it from a FragmentContext provided near the fragment root. The header fragment uses this hook because it renders the Cloudflare and Github logos as its own assets; its Image component then accesses the context to build correct paths.

Parent-to-child requests in the demo go through Cloudflare's service bindings, which avoid a network round-trip between fragments. Each fragment stays independently deployable, but composition during a request has near-zero added communication cost.

What separates this from other approaches

Three characteristics distinguish fragment composition on Workers from existing micro-frontend patterns.

Fragments are server-side rendered applications composed on the server, unlike monoliths or client-side micro-frontends. Rendering happens before the browser receives the page, so first paint is not blocked by framework bootstrap or a cascade of remote component downloads. In contrast to Node.js or cloud-function micro-frontends, Workers runs on a globally distributed, region-less runtime. Fragments have inherently low latency, and the service-binding mechanism means coordination between them is far cheaper than HTTP calls between separate functions.

Compared with module federation, fragment JavaScript stays specific to the fragment that supports it. Because each bundle is small, teams do not have to share library code between fragments. That removes the need for coordinated upgrades of shared dependencies and the version-skew bugs that come with them.

Open questions in the demo

The Cloud Gallery is a proof of concept. Its architecture suggests several directions worth investigating.

Per-fragment caching

A page request today renders every fragment upstream. Caching changes that: fragments whose content is static can be cached independently, so a request re-renders only the fragments whose content has changed. The HTML response reaches the browser sooner, and compute is not spent re-generating unchanged markup.

Fragment-based routing

The same composition model could replace single-page composition with page routing. During SSR the root fragment would pick and insert a page fragment based on the requested URL. During a client-side navigation, the root would swap the displayed page fragment rather than tearing down the whole app. That combines server rendering for the first paint with fast client-side transitions afterward.

Other frontend frameworks

Every fragment in Cloud Gallery runs Qwik, but that is not a constraint. Any framework capable of server-side rendering and shipping little client-side JavaScript could drive a fragment. Mixing frameworks within one application is also possible in principle. HTML streaming is not required, but frameworks that support it will parallelize large-page rendering better.

Incremental adoption

Refactoring a legacy application fully into fragments is a large, risky investment. A saner path is migrating one UI piece at a time: wrap a single section as a fragment served by Workers, keep the rest of the legacy stack untouched, and iterate. Subsequent migrations move further sections over as teams build confidence.

Reducing configuration

Standing up a fragment today involves a lot of mechanical setup. Conventions and tooling along the lines of Rails or filesystem-based routing meta-frameworks could eliminate most of that scaffolding, leaving developers to express fragments declaratively rather than by wiring configuration by hand.

A note on positioning

Micro-frontends have historically delivered mixed results in the move toward scaling large applications. The fragment model built on Workers addresses the main weaknesses of earlier approaches — server-side rendering preserves browser performance, distributed deployment eliminates the coordination overhead between fragments, and small per-fragment payloads avoid dependency-management problems. The demo code is available in the GitHub repository and runs on Workers' free plan if you want to deploy it and explore the trade-offs yourself.