What we are building
In Part 1, I explained when microfrontends are useful. Now let's build a small example.
Our shop has three Next.js applications. The shell owns the home page and navigation. Catalog owns search and product pages. Checkout owns the cart and payment flow.
We will use Next.js Multi-Zones. Each application owns a group of routes, and the shell sends requests to the right application. To the visitor, everything still appears under one domain.
Use three simple applications
The applications can live in separate repositories, but I would begin with an npm workspaces monorepo. It is easier to run everything locally and share a few small packages.
Keep shared packages boring: design tokens, small types, and logging helpers. Catalog and Checkout business rules should not be shared.
commerce/
apps/
shell/ # home, navigation, and routing
catalog/ # /products/* and /search
checkout/ # /cart and /checkout/*
packages/
design-tokens/
contracts/Give each zone its own assets
Every Next.js application creates JavaScript and CSS files under its internal asset path. If several apps use the same path, the browser can receive files from the wrong deployment.
Next.js solves this with assetPrefix. Catalog and Checkout each get a unique prefix. The shell is the default application, so it does not need one.
// apps/catalog/next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
assetPrefix: "/catalog-static",
};
export default nextConfig;// apps/checkout/next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
assetPrefix: "/checkout-static",
};
export default nextConfig;Send routes to the right application
The public domain points to the shell. Rewrites send Catalog and Checkout requests to their own servers without changing the URL in the browser.
Each route must have one owner. If the shell and Catalog both try to serve /products, the result becomes confusing and depends on routing order.
The environment variables can point to localhost during development and to the real deployment addresses in production.
// apps/shell/next.config.ts
import type { NextConfig } from "next";
const catalog = process.env.CATALOG_ORIGIN!;
const checkout = process.env.CHECKOUT_ORIGIN!;
const nextConfig: NextConfig = {
async rewrites() {
return [
{ source: "/products/:path*", destination: catalog + "/products/:path*" },
{ source: "/search", destination: catalog + "/search" },
{ source: "/catalog-static/:path*", destination: catalog + "/catalog-static/:path*" },
{ source: "/cart", destination: checkout + "/cart" },
{ source: "/checkout/:path*", destination: checkout + "/checkout/:path*" },
{ source: "/checkout-static/:path*", destination: checkout + "/checkout-static/:path*" },
];
},
};
export default nextConfig;Use normal links between zones
Next.js can do fast client-side navigation inside one application. Moving to another zone is different: the current app unloads and the new one starts.
For this reason, use next/link inside a zone and a normal anchor when a link crosses into another zone.
A cross-zone visit is a full page load, so keep pages that people switch between constantly in the same zone when possible.
// This goes from Catalog to Checkout, so use a normal link.
export function CartLink({ count }: { count: number }) {
return <a href="/cart">Cart ({count})</a>;
}
// This stays inside Catalog, so next/link is fine.
import Link from "next/link";
export function ProductLink({ slug }: { slug: string }) {
return <Link href={"/products/" + slug}>View product</Link>;
}Share only what is needed
The visitor should not sign in again when moving between apps. A secure HTTP-only session cookie on the public domain can be sent with each request, and every zone can validate the same small session contract.
Do not try to keep one large Redux store synchronized across applications. Store important data such as carts and orders behind APIs. Keep temporary UI state inside the zone that uses it.
The same rule applies to design. Share tokens and basic accessible components, but let each domain own its screens and business flows.
Plan for failure
Separate deployments are helpful only when one broken app does not break everything. If Checkout is down, visitors should still be able to browse Catalog.
Use Next.js loading and error boundaries, keep important state on the server, and make recovery messages honest. If payment is unavailable, tell the customer clearly instead of pretending the order succeeded.
// apps/checkout/app/checkout/error.tsx
"use client";
export default function CheckoutError({ reset }: { reset: () => void }) {
return (
<section role="alert">
<h2>Checkout is temporarily unavailable</h2>
<p>Your cart is safe. Please try again.</p>
<button onClick={reset}>Try again</button>
</section>
);
}Test the boundaries
Each application should test its own pages. Then add a small number of tests for journeys that cross zones, such as product to cart and cart to confirmation.
Also test the cases that make this architecture useful: deploy Catalog without rebuilding Checkout, roll one application back, and make one zone temporarily unavailable.
Logs should include the zone name, release version, route, and request ID. That makes it much easier to follow one customer request across several deployments.
The full monorepo structure
Here is how all the pieces from this example can fit together. Each application owns its routes and deployment configuration, while the packages folder contains only the small contracts and tools that every zone needs.
Open the folders to explore the files. A real project may add more test and deployment configuration, but this is the complete application structure used in this example.
Final thoughts
The code is not the hardest part of microfrontends. The hard part is keeping several applications consistent and reliable while teams release them independently.
I would start with one Next.js application and clear domain folders. When teams truly need separate releases, move one complete route group behind rewrites and keep the public URL unchanged.
The official Next.js Multi-Zones guide covers the same main pieces: asset prefixes, rewrites, and normal links between zones.
Add another zone only when the first one actually makes ownership and releases easier.