From 6a760b95540e490f94f793e197c1d5a0c8b020e7 Mon Sep 17 00:00:00 2001 From: Brendan Irvine-Broque Date: Tue, 30 Dec 2025 09:01:39 -0800 Subject: [PATCH 1/3] Add microfrontends guide for Workers Document microfrontend architecture using router workers and service bindings, including routing logic, path rewriting, asset handling, preloading, and deployment workflows. --- .../web-apps/microfrontends.mdx | 315 ++++++++++++++++++ 1 file changed, 315 insertions(+) create mode 100644 src/content/docs/workers/framework-guides/web-apps/microfrontends.mdx diff --git a/src/content/docs/workers/framework-guides/web-apps/microfrontends.mdx b/src/content/docs/workers/framework-guides/web-apps/microfrontends.mdx new file mode 100644 index 000000000000000..af5c0b014fe58a7 --- /dev/null +++ b/src/content/docs/workers/framework-guides/web-apps/microfrontends.mdx @@ -0,0 +1,315 @@ +--- +pcx_content_type: concept +title: Microfrontends +sidebar: + order: 8 +description: Split a single application into independently deployable frontends, using a router worker and service bindings +--- + +import { Description, Details, Tabs, TabItem } from "~/components"; + +Microfrontends let you split a single application into smaller, independently deployable units that render as one cohesive application. Different teams using different technologies can develop, test, and deploy each microfrontend. + +Use microfrontends when you want to: + +- Enable many teams to deploy independently without coordinating releases +- Gradually migrate from a monolith to a distributed architecture +- Build multi-framework applications (for example, Astro, Remix, and Next.js in one app) + +## Get started + +Create a microfrontend project: + +[![Deploy to Cloudflare](https://deploy.workers.cloudflare.com/button)](https://dash.cloudflare.com/?to=/:account/workers-and-pages/create?type=vmfe) + +This template automatically creates a router worker with pre-configured routing logic, and lets you configure [Service bindings](/workers/runtime-apis/bindings/service-bindings/) to Workers you have already deployed to your Cloudflare account. + +## How it works + +```mermaid +graph LR + A[Browser Request] --> B[Router Worker] + B -->|Service Binding| C[Microfrontend A] + B -->|Service Binding| D[Microfrontend B] + B -->|Service Binding| E[Microfrontend C] +``` + +The router worker: + +1. Analyzes the incoming request path +2. Matches it against configured routes +3. Forwards the request to the appropriate microfrontend via service binding +4. Rewrites HTML, CSS, and headers to ensure assets load correctly +5. Returns the response to the browser + +Each microfrontend can be: + +- A full-framework application (Next.js, SvelteKit, Astro, etc.) +- A static site with [Workers Static Assets](/workers/static-assets/) +- Built with different frameworks and technologies + +## Routing logic + +The router worker uses a `ROUTES` [environment variable](/workers/configuration/environment-variables/) to determine which microfrontend handles each path. Routes are matched by specificity, with longer paths taking precedence. + +Example `ROUTES` configuration: + +```json +{ + "routes": [ + { "path": "/app-a", "binding": "MICROFRONTEND_A", "preload": true }, + { "path": "/app-b", "binding": "MICROFRONTEND_B", "preload": true }, + { "path": "/", "binding": "MICROFRONTEND_HOME" } + ], + "smoothTransitions": true +} +``` + +Each route requires: + +- `path`: The mount path for the microfrontend (must be distinct from other routes) +- `binding`: The name of the service binding in your [Wrangler configuration file](/workers/wrangler/configuration/) +- `preload` (optional): Whether to prefetch this microfrontend for faster navigation + +When a request comes in for `/app-a/dashboard`, the router: + +1. Matches it to the `/app-a` route +2. Forwards the request to `MICROFRONTEND_A` +3. Strips the `/app-a` prefix, so the microfrontend receives `/dashboard` + +The router includes path matching logic that supports: + +```typescript +// Static paths +{ "path": "/dashboard" } + +// Dynamic parameters +{ "path": "/users/:id" } + +// Wildcard matching (zero or more segments) +{ "path": "/docs/:path*" } + +// Required segments (one or more segments) +{ "path": "/api/:path+" } +``` + +## Path rewriting + +The router worker uses [HTMLRewriter](/workers/runtime-apis/html-rewriter/) to automatically rewrite HTML attributes to include the mount path prefix, ensuring assets load from the correct location. + +When a microfrontend mounted at `/app-a` returns HTML: + +```html + + + +``` + +The router rewrites it to: + +```html + + + +``` + +The rewriter handles these attributes across all HTML elements: + +- `href`, `src`, `poster`, `action`, `srcset` +- `data-*` attributes like `data-src`, `data-href`, `data-background` +- Framework-specific attributes like `astro-component-url` + +The router only rewrites paths that start with configured asset prefixes to avoid breaking external URLs: + +```javascript +// Default asset prefixes +const DEFAULT_ASSET_PREFIXES = [ + "/assets/", + "/static/", + "/build/", + "/_astro/", + "/fonts/", +]; +``` + +Most frameworks work with the default prefixes. For frameworks with different build outputs (like Next.js which uses `/_next/`), you can configure custom prefixes using the `ASSET_PREFIXES` [environment variable](/workers/configuration/environment-variables/): + +```json +["/_next/", "/public/"] +``` + +## Asset handling + +The router also rewrites CSS files to ensure `url()` references work correctly. When a microfrontend mounted at `/app-a` returns CSS: + +```css +.hero { + background: url(/assets/hero.jpg); +} + +.icon { + background: url("/static/icon.svg"); +} +``` + +The router rewrites it to: + +```css +.hero { + background: url(/app-a/assets/hero.jpg); +} + +.icon { + background: url("/app-a/static/icon.svg"); +} +``` + +The router also handles: + +- **Redirect headers**: Rewrites `Location` headers to include the mount path +- **Cookie paths**: Updates `Set-Cookie` headers to scope cookies to the mount path + +## Preloading + +The router can generate a preload script that prefetches other microfrontends in the background, improving navigation performance between different parts of your application. + +When you set `"preload": true` on a route, the router: + +1. Generates a `/__mf-preload.js` script +2. Injects it into HTML responses +3. Prefetches configured microfrontends after page load + +Example preload script (automatically generated): + +```javascript +// Injected by router worker +const routes = ["/app-a", "/app-b"]; +const run = () => { + for (const p of routes) { + fetch(p, { + method: "GET", + credentials: "same-origin", + cache: "default", + }).catch(() => {}); + } +}; + +if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", run, { once: true }); +} else { + run(); +} +``` + +This allows browsers to fetch and [cache](/cache/) resources from other microfrontends before users navigate to them, resulting in faster transitions. + +## Smooth transitions + +You can enable smooth page transitions between microfrontends using the [View Transitions API](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API). + +To enable smooth transitions, set `"smoothTransitions": true` in your `ROUTES` configuration: + +```json +{ + "routes": [ + { "path": "/app-a", "binding": "MICROFRONTEND_A" }, + { "path": "/app-b", "binding": "MICROFRONTEND_B" } + ], + "smoothTransitions": true +} +``` + +The router automatically injects CSS into HTML responses: + +```css +@supports (view-transition-name: none) { + ::view-transition-old(root), + ::view-transition-new(root) { + animation-duration: 0.3s; + animation-timing-function: ease-in-out; + } + main { + view-transition-name: main-content; + } + nav { + view-transition-name: navigation; + } +} +``` + +This feature only works in browsers that support the View Transitions API. Browsers without support will navigate normally without animations. + +## Add a new microfrontend + +To add a new microfrontend to your application after initial setup: + +1. **Create and deploy the new microfrontend worker** + + Deploy your new microfrontend as a separate Worker. This can be a [framework application](/workers/framework-guides/) (Next.js, Astro, etc.) or a static site with [Workers Static Assets](/workers/static-assets/). + +2. **Add a [service binding](/workers/runtime-apis/bindings/service-bindings/) in your router's Wrangler configuration file** + + + ```toml + [[services]] + binding = "MICROFRONTEND_C" + service = "my-new-microfrontend" + ``` + + +3. **Update the `ROUTES` environment variable** + + Add your new route to the `ROUTES` configuration: + + ```json + { + "routes": [ + { "path": "/app-a", "binding": "MICROFRONTEND_A", "preload": true }, + { "path": "/app-b", "binding": "MICROFRONTEND_B", "preload": true }, + { "path": "/app-c", "binding": "MICROFRONTEND_C", "preload": true }, + { "path": "/", "binding": "MICROFRONTEND_HOME" } + ] + } + ``` + +4. **Redeploy the router worker** + + ```sh + npx wrangler deploy + ``` + +Your new microfrontend is now accessible at the configured path (for example, `/app-c`). + +## Local development + +During development, you can test your microfrontend architecture locally using Wrangler's service binding support. Run the the router Worker locally using `wrangler dev`, and then in separate terminals run each of the microfrontends. + +If you only need to work on one of the microfrontends, you can run the others remotely using [remote bindings](/workers/development-testing/#remote-bindings), without needing to have access to the source code or run a local dev server. + +For each microfrontend you want to run remotely while in local dev, configure its service binding with the remote flag: + + +```json +{ +"services": [ + { + "binding": "", + "service": "", + "remote": true + } +] +} +``` + + +## Deployment + +Each microfrontend can be deployed independently without redeploying the router or other microfrontends. This enables teams to: + +- Deploy updates on their own schedule +- Roll back individual microfrontends without affecting others +- Test and release features independently + +When you deploy a microfrontend worker, the router automatically routes requests to the latest version via the service binding. No router changes are required unless you are adding new routes or updating the `ROUTES` configuration. + +To deploy to production, you can use [custom domains](/workers/configuration/routing/custom-domains/) for your router worker, and configure [Workers Builds](/workers/ci-cd/builds/) for continuous deployment from your Git repository. From a27e77294cabcd1b06798d284375ceb0d3ccecb3 Mon Sep 17 00:00:00 2001 From: Brendan Irvine-Broque Date: Tue, 30 Dec 2025 10:24:57 -0800 Subject: [PATCH 2/3] Update mermaid diagram styling --- src/scripts/MERMAID.md | 54 +++++++++++++++++++ src/scripts/mermaid.ts | 119 ++++++++++++++++++++++++++++++++++++++--- src/styles/mermaid.css | 72 +++++++++++++++++++++++++ 3 files changed, 239 insertions(+), 6 deletions(-) create mode 100644 src/scripts/MERMAID.md diff --git a/src/scripts/MERMAID.md b/src/scripts/MERMAID.md new file mode 100644 index 000000000000000..96cb6a43333937c --- /dev/null +++ b/src/scripts/MERMAID.md @@ -0,0 +1,54 @@ +# Mermaid Diagram Rendering + +Client-side rendering of Mermaid diagrams with Cloudflare branding (orange `#f6821f`), light/dark theme support, and annotation footers. + +## Usage + +### Basic Diagram + +````markdown +```mermaid +flowchart LR +A[Client] --> B[Server] +B --> C[Database] +``` +```` + +### With Title and Accessibility + +````markdown +```mermaid +flowchart LR +accTitle: Workers for Platforms - Main Flow +accDescr: Shows how requests are routed through the platform + +A[Browser Request] --> B[Router Worker] +B -->|Service Binding| C[User Worker A] +B -->|Service Binding| D[User Worker B] +``` +```` + +The `accTitle` appears in the annotation footer. Always include `accDescr` for screen readers. + +## How It Works + +1. Rehype plugin (`src/plugins/rehype/mermaid.ts`) transforms markdown code blocks to `
`
+2. Client script (`src/scripts/mermaid.ts`) renders diagrams as SVG with custom theme variables
+3. Theme changes trigger automatic re-rendering via `MutationObserver`
+
+## Customization
+
+- **Theme variables**: Edit `mermaid.ts`
+- **Container styles**: Edit `src/styles/mermaid.css`
+- See [Mermaid theming docs](https://mermaid.js.org/config/theming.html)
+
+## Troubleshooting
+
+- **Not rendering**: Check browser console, validate syntax at [mermaid.live](https://mermaid.live/)
+- **No annotation**: Ensure `accTitle` is included in diagram definition
+
+## Related Files
+
+- `src/scripts/mermaid.ts` - Rendering script
+- `src/styles/mermaid.css` - Styles
+- `src/plugins/rehype/mermaid.ts` - Markdown transformer
diff --git a/src/scripts/mermaid.ts b/src/scripts/mermaid.ts
index 7698f76482a681f..04ae1b826a64287 100644
--- a/src/scripts/mermaid.ts
+++ b/src/scripts/mermaid.ts
@@ -4,11 +4,96 @@ const diagrams = document.querySelectorAll("pre.mermaid");
 
 let init = false;
 
+// Get computed font family from CSS variable
+function getFontFamily(): string {
+	const computedStyle = getComputedStyle(document.documentElement);
+	const slFont = computedStyle.getPropertyValue("--__sl-font").trim();
+	return slFont || "system-ui, -apple-system, sans-serif";
+}
+
+// Create wrapper container with annotation
+function wrapDiagram(diagram: HTMLPreElement, title: string | null) {
+	// Skip if already wrapped
+	if (diagram.parentElement?.classList.contains("mermaid-container")) {
+		return;
+	}
+
+	// Create container
+	const container = document.createElement("div");
+	container.className = "mermaid-container";
+
+	// Wrap the diagram
+	diagram.parentNode?.insertBefore(container, diagram);
+	container.appendChild(diagram);
+
+	// Add annotation footer if title exists
+	if (title) {
+		const footer = document.createElement("div");
+		footer.className = "mermaid-annotation";
+
+		const titleSpan = document.createElement("span");
+		titleSpan.className = "mermaid-annotation-title";
+		titleSpan.textContent = title;
+
+		const logo = document.createElement("img");
+		logo.src = "/logo.svg";
+		logo.alt = "Cloudflare";
+		logo.className = "mermaid-annotation-logo";
+
+		footer.appendChild(titleSpan);
+		footer.appendChild(logo);
+		container.appendChild(footer);
+	}
+}
+
 async function render() {
-	const theme =
-		document.documentElement.getAttribute("data-theme") === "light"
-			? "neutral"
-			: "dark";
+	const isLight =
+		document.documentElement.getAttribute("data-theme") === "light";
+	const fontFamily = getFontFamily();
+
+	// Custom theme variables for Cloudflare branding
+	const lightThemeVars = {
+		fontFamily,
+		primaryColor: "#fef1e6", // cl1-orange-9 (very light orange for node backgrounds)
+		primaryBorderColor: "#f6821f", // cl1-brand-orange
+		primaryTextColor: "#1d1d1d", // cl1-gray-0
+		secondaryColor: "#f2f2f2", // cl1-gray-9
+		secondaryBorderColor: "#999999", // cl1-gray-6
+		secondaryTextColor: "#1d1d1d", // cl1-gray-0
+		tertiaryColor: "#f2f2f2", // cl1-gray-9
+		tertiaryBorderColor: "#999999", // cl1-gray-6
+		tertiaryTextColor: "#1d1d1d", // cl1-gray-0
+		lineColor: "#f6821f", // cl1-brand-orange for arrows
+		textColor: "#1d1d1d", // cl1-gray-0
+		mainBkg: "#fef1e6", // cl1-orange-9
+		errorBkgColor: "#ffefee", // cl1-red-9
+		errorTextColor: "#3c0501", // cl1-red-0
+		edgeLabelBackground: "#ffffff", // white background for edge labels in light mode
+		labelBackground: "#ffffff", // white background for labels in light mode
+	};
+
+	const darkThemeVars = {
+		fontFamily,
+		primaryColor: "#482303", // cl1-orange-1 (dark orange for node backgrounds)
+		primaryBorderColor: "#f6821f", // cl1-brand-orange
+		primaryTextColor: "#f2f2f2", // cl1-gray-9
+		secondaryColor: "#313131", // cl1-gray-1
+		secondaryBorderColor: "#797979", // cl1-gray-5
+		secondaryTextColor: "#f2f2f2", // cl1-gray-9
+		tertiaryColor: "#313131", // cl1-gray-1
+		tertiaryBorderColor: "#797979", // cl1-gray-5
+		tertiaryTextColor: "#f2f2f2", // cl1-gray-9
+		lineColor: "#f6821f", // cl1-brand-orange for arrows
+		textColor: "#f2f2f2", // cl1-gray-9
+		mainBkg: "#482303", // cl1-orange-1
+		background: "#1d1d1d", // cl1-gray-0
+		errorBkgColor: "#3c0501", // cl1-red-0
+		errorTextColor: "#ffefee", // cl1-red-9
+		edgeLabelBackground: "#1d1d1d", // dark background for edge labels
+		labelBackground: "#1d1d1d", // dark background for labels
+	};
+
+	const themeVariables = isLight ? lightThemeVars : darkThemeVars;
 
 	for (const diagram of diagrams) {
 		if (!init) {
@@ -17,10 +102,30 @@ async function render() {
 
 		const def = diagram.getAttribute("data-diagram") as string;
 
-		mermaid.initialize({ startOnLoad: false, theme });
+		// Initialize with base theme and custom variables
+		mermaid.initialize({
+			startOnLoad: false,
+			theme: "base",
+			themeVariables,
+			flowchart: {
+				htmlLabels: true,
+				useMaxWidth: true,
+			},
+		});
+
 		await mermaid
 			.render(`mermaid-${crypto.randomUUID()}`, def)
-			.then(({ svg }) => (diagram.innerHTML = svg));
+			.then(({ svg }) => {
+				diagram.innerHTML = svg;
+
+				// Extract title from SVG for annotation
+				const svgElement = diagram.querySelector("svg");
+				const titleElement = svgElement?.querySelector("title");
+				const title = titleElement?.textContent?.trim() || null;
+
+				// Wrap diagram with container and annotation
+				wrapDiagram(diagram, title);
+			});
 
 		diagram.setAttribute("data-processed", "true");
 	}
@@ -34,3 +139,5 @@ obs.observe(document.documentElement, {
 	attributes: true,
 	attributeFilter: ["data-theme"],
 });
+
+render();
diff --git a/src/styles/mermaid.css b/src/styles/mermaid.css
index 84b443be558081d..920d8652a32d844 100644
--- a/src/styles/mermaid.css
+++ b/src/styles/mermaid.css
@@ -1,3 +1,75 @@
+/* Hide unprocessed diagrams to prevent flash of unstyled content */
 pre.mermaid:not([data-processed]) {
 	visibility: hidden;
 }
+
+/* Container wrapper for diagram + annotation */
+.mermaid-container {
+	background: transparent;
+	overflow: hidden;
+	margin: 1rem 0;
+	border-radius: 0;
+	box-shadow: none;
+}
+
+/* The diagram itself */
+pre.mermaid[data-processed] {
+	padding: 1.5rem;
+	margin: 0;
+	background: transparent;
+	border-radius: 0;
+	box-shadow: none;
+	display: block;
+	line-height: 0;
+	border-color: var(--sl-color-hairline) !important;
+}
+
+/* Ensure SVG fills the width nicely */
+pre.mermaid[data-processed] svg {
+	max-width: 100%;
+	height: auto;
+	display: block;
+	margin: 0;
+	padding: 0;
+	vertical-align: top;
+}
+
+/* Remove any box shadow from SVG elements */
+pre.mermaid[data-processed] svg * {
+	box-shadow: none !important;
+}
+
+/* Annotation footer */
+.mermaid-annotation {
+	display: flex;
+	justify-content: space-between;
+	align-items: center;
+	padding: 0.75rem 1rem;
+	font-size: 0.75rem;
+	font-weight: 600;
+	text-transform: uppercase;
+	letter-spacing: 0.05em;
+	color: var(--sl-color-gray-3);
+	background: rgba(0, 0, 0, 0.02);
+	margin: 0;
+}
+
+.mermaid-annotation-title {
+	flex: 1;
+}
+
+.mermaid-annotation-logo {
+	height: 16px;
+	width: auto;
+	opacity: 0.7;
+}
+
+/* Dark mode adjustments */
+:root[data-theme="dark"] .mermaid-annotation {
+	background: rgba(255, 255, 255, 0.03);
+	color: var(--sl-color-gray-2);
+}
+
+:root[data-theme="dark"] .mermaid-annotation-logo {
+	opacity: 0.8;
+}

From c071c67ca9cf37e076b2ecafb2852ca716658b4c Mon Sep 17 00:00:00 2001
From: Brendan Irvine-Broque 
Date: Tue, 30 Dec 2025 10:33:34 -0800
Subject: [PATCH 3/3] Remove

---
 .../web-apps/microfrontends.mdx               | 315 ------------------
 1 file changed, 315 deletions(-)
 delete mode 100644 src/content/docs/workers/framework-guides/web-apps/microfrontends.mdx

diff --git a/src/content/docs/workers/framework-guides/web-apps/microfrontends.mdx b/src/content/docs/workers/framework-guides/web-apps/microfrontends.mdx
deleted file mode 100644
index af5c0b014fe58a7..000000000000000
--- a/src/content/docs/workers/framework-guides/web-apps/microfrontends.mdx
+++ /dev/null
@@ -1,315 +0,0 @@
----
-pcx_content_type: concept
-title: Microfrontends
-sidebar:
-  order: 8
-description: Split a single application into independently deployable frontends, using a router worker and service bindings
----
-
-import { Description, Details, Tabs, TabItem } from "~/components";
-
-Microfrontends let you split a single application into smaller, independently deployable units that render as one cohesive application. Different teams using different technologies can develop, test, and deploy each microfrontend.
-
-Use microfrontends when you want to:
-
-- Enable many teams to deploy independently without coordinating releases
-- Gradually migrate from a monolith to a distributed architecture
-- Build multi-framework applications (for example, Astro, Remix, and Next.js in one app)
-
-## Get started
-
-Create a microfrontend project:
-
-[![Deploy to Cloudflare](https://deploy.workers.cloudflare.com/button)](https://dash.cloudflare.com/?to=/:account/workers-and-pages/create?type=vmfe)
-
-This template automatically creates a router worker with pre-configured routing logic, and lets you configure [Service bindings](/workers/runtime-apis/bindings/service-bindings/) to Workers you have already deployed to your Cloudflare account.
-
-## How it works
-
-```mermaid
-graph LR
-    A[Browser Request] --> B[Router Worker]
-    B -->|Service Binding| C[Microfrontend A]
-    B -->|Service Binding| D[Microfrontend B]
-    B -->|Service Binding| E[Microfrontend C]
-```
-
-The router worker:
-
-1. Analyzes the incoming request path
-2. Matches it against configured routes
-3. Forwards the request to the appropriate microfrontend via service binding
-4. Rewrites HTML, CSS, and headers to ensure assets load correctly
-5. Returns the response to the browser
-
-Each microfrontend can be:
-
-- A full-framework application (Next.js, SvelteKit, Astro, etc.)
-- A static site with [Workers Static Assets](/workers/static-assets/)
-- Built with different frameworks and technologies
-
-## Routing logic
-
-The router worker uses a `ROUTES` [environment variable](/workers/configuration/environment-variables/) to determine which microfrontend handles each path. Routes are matched by specificity, with longer paths taking precedence.
-
-Example `ROUTES` configuration:
-
-```json
-{
-	"routes": [
-		{ "path": "/app-a", "binding": "MICROFRONTEND_A", "preload": true },
-		{ "path": "/app-b", "binding": "MICROFRONTEND_B", "preload": true },
-		{ "path": "/", "binding": "MICROFRONTEND_HOME" }
-	],
-	"smoothTransitions": true
-}
-```
-
-Each route requires:
-
-- `path`: The mount path for the microfrontend (must be distinct from other routes)
-- `binding`: The name of the service binding in your [Wrangler configuration file](/workers/wrangler/configuration/)
-- `preload` (optional): Whether to prefetch this microfrontend for faster navigation
-
-When a request comes in for `/app-a/dashboard`, the router:
-
-1. Matches it to the `/app-a` route
-2. Forwards the request to `MICROFRONTEND_A`
-3. Strips the `/app-a` prefix, so the microfrontend receives `/dashboard`
-
-The router includes path matching logic that supports:
-
-```typescript
-// Static paths
-{ "path": "/dashboard" }
-
-// Dynamic parameters
-{ "path": "/users/:id" }
-
-// Wildcard matching (zero or more segments)
-{ "path": "/docs/:path*" }
-
-// Required segments (one or more segments)
-{ "path": "/api/:path+" }
-```
-
-## Path rewriting
-
-The router worker uses [HTMLRewriter](/workers/runtime-apis/html-rewriter/) to automatically rewrite HTML attributes to include the mount path prefix, ensuring assets load from the correct location.
-
-When a microfrontend mounted at `/app-a` returns HTML:
-
-```html
-
-
-
-```
-
-The router rewrites it to:
-
-```html
-
-
-
-```
-
-The rewriter handles these attributes across all HTML elements:
-
-- `href`, `src`, `poster`, `action`, `srcset`
-- `data-*` attributes like `data-src`, `data-href`, `data-background`
-- Framework-specific attributes like `astro-component-url`
-
-The router only rewrites paths that start with configured asset prefixes to avoid breaking external URLs:
-
-```javascript
-// Default asset prefixes
-const DEFAULT_ASSET_PREFIXES = [
-	"/assets/",
-	"/static/",
-	"/build/",
-	"/_astro/",
-	"/fonts/",
-];
-```
-
-Most frameworks work with the default prefixes. For frameworks with different build outputs (like Next.js which uses `/_next/`), you can configure custom prefixes using the `ASSET_PREFIXES` [environment variable](/workers/configuration/environment-variables/):
-
-```json
-["/_next/", "/public/"]
-```
-
-## Asset handling
-
-The router also rewrites CSS files to ensure `url()` references work correctly. When a microfrontend mounted at `/app-a` returns CSS:
-
-```css
-.hero {
-	background: url(/assets/hero.jpg);
-}
-
-.icon {
-	background: url("/static/icon.svg");
-}
-```
-
-The router rewrites it to:
-
-```css
-.hero {
-	background: url(/app-a/assets/hero.jpg);
-}
-
-.icon {
-	background: url("/app-a/static/icon.svg");
-}
-```
-
-The router also handles:
-
-- **Redirect headers**: Rewrites `Location` headers to include the mount path
-- **Cookie paths**: Updates `Set-Cookie` headers to scope cookies to the mount path
-
-## Preloading
-
-The router can generate a preload script that prefetches other microfrontends in the background, improving navigation performance between different parts of your application.
-
-When you set `"preload": true` on a route, the router:
-
-1. Generates a `/__mf-preload.js` script
-2. Injects it into HTML responses
-3. Prefetches configured microfrontends after page load
-
-Example preload script (automatically generated):
-
-```javascript
-// Injected by router worker
-const routes = ["/app-a", "/app-b"];
-const run = () => {
-	for (const p of routes) {
-		fetch(p, {
-			method: "GET",
-			credentials: "same-origin",
-			cache: "default",
-		}).catch(() => {});
-	}
-};
-
-if (document.readyState === "loading") {
-	document.addEventListener("DOMContentLoaded", run, { once: true });
-} else {
-	run();
-}
-```
-
-This allows browsers to fetch and [cache](/cache/) resources from other microfrontends before users navigate to them, resulting in faster transitions.
-
-## Smooth transitions
-
-You can enable smooth page transitions between microfrontends using the [View Transitions API](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API).
-
-To enable smooth transitions, set `"smoothTransitions": true` in your `ROUTES` configuration:
-
-```json
-{
-	"routes": [
-		{ "path": "/app-a", "binding": "MICROFRONTEND_A" },
-		{ "path": "/app-b", "binding": "MICROFRONTEND_B" }
-	],
-	"smoothTransitions": true
-}
-```
-
-The router automatically injects CSS into HTML responses:
-
-```css
-@supports (view-transition-name: none) {
-	::view-transition-old(root),
-	::view-transition-new(root) {
-		animation-duration: 0.3s;
-		animation-timing-function: ease-in-out;
-	}
-	main {
-		view-transition-name: main-content;
-	}
-	nav {
-		view-transition-name: navigation;
-	}
-}
-```
-
-This feature only works in browsers that support the View Transitions API. Browsers without support will navigate normally without animations.
-
-## Add a new microfrontend
-
-To add a new microfrontend to your application after initial setup:
-
-1. **Create and deploy the new microfrontend worker**
-
-   Deploy your new microfrontend as a separate Worker. This can be a [framework application](/workers/framework-guides/) (Next.js, Astro, etc.) or a static site with [Workers Static Assets](/workers/static-assets/).
-
-2. **Add a [service binding](/workers/runtime-apis/bindings/service-bindings/) in your router's Wrangler configuration file**
-
-	
-	```toml
-   [[services]]
-   binding = "MICROFRONTEND_C"
-   service = "my-new-microfrontend"
-	```
-	
-
-3. **Update the `ROUTES` environment variable**
-
-   Add your new route to the `ROUTES` configuration:
-
-   ```json
-   {
-   	"routes": [
-   		{ "path": "/app-a", "binding": "MICROFRONTEND_A", "preload": true },
-   		{ "path": "/app-b", "binding": "MICROFRONTEND_B", "preload": true },
-   		{ "path": "/app-c", "binding": "MICROFRONTEND_C", "preload": true },
-   		{ "path": "/", "binding": "MICROFRONTEND_HOME" }
-   	]
-   }
-   ```
-
-4. **Redeploy the router worker**
-
-   ```sh
-   npx wrangler deploy
-   ```
-
-Your new microfrontend is now accessible at the configured path (for example, `/app-c`).
-
-## Local development
-
-During development, you can test your microfrontend architecture locally using Wrangler's service binding support. Run the the router Worker locally using `wrangler dev`, and then in separate terminals run each of the microfrontends.
-
-If you only need to work on one of the microfrontends, you can run the others remotely using [remote bindings](/workers/development-testing/#remote-bindings), without needing to have access to the source code or run a local dev server.
-
-For each microfrontend you want to run remotely while in local dev, configure its service binding with the remote flag:
-
-
-```json
-{
-"services": [
-	{
-	"binding": "",
-	"service": "",
-	"remote": true
-	}
-]
-}
-```
-
-
-## Deployment
-
-Each microfrontend can be deployed independently without redeploying the router or other microfrontends. This enables teams to:
-
-- Deploy updates on their own schedule
-- Roll back individual microfrontends without affecting others
-- Test and release features independently
-
-When you deploy a microfrontend worker, the router automatically routes requests to the latest version via the service binding. No router changes are required unless you are adding new routes or updating the `ROUTES` configuration.
-
-To deploy to production, you can use [custom domains](/workers/configuration/routing/custom-domains/) for your router worker, and configure [Workers Builds](/workers/ci-cd/builds/) for continuous deployment from your Git repository.