Micro-Frontends with Next.js, Multi-Zones, and Turborepo: A Practical Guide

Building scalable frontend applications is as important today as having an internet connection itself. As your product grows, your frontend codebase can quickly turn into a messy jungle. That’s where micro-frontends come in.
In this article, I’ll explain what micro-frontends are, why you might want to use them, how Next.js makes them easier with multi-zones, and how Turborepo ties it all together for efficient development.
What are Micro-Frontends?
In simple terms, micro-frontends means breaking a large, complex frontend app into smaller apps that can be developed, deployed, and maintained independently.
Think of it like a city made of neighborhoods. Each neighborhood has its own houses, roads, and rules, but together they form a single city.
A practical example:
- Your home page runs on Next.js
- Your dashboard runs on React
- Your blog runs on Nuxt
They can all live on the same domain without users even realizing they are different apps.
The biggest benefits are:
- Teams can work independently without stepping on each other’s toes
- You can mix different tech stacks
- You can deploy parts of your site without redeploying everything
The main drawback is complexity. Developers need to understand how to integrate multiple apps without breaking the user experience.
Next.js and Micro-Frontends
Micro-frontends usually come in two flavors:
-
Build-time integration All micro-frontends are built together into one application. This is simple, but you lose the independence of separate deployments.
-
Run-time integration Each app is built and deployed separately, then stitched together at runtime. This is more powerful and aligns with the micro-frontend philosophy.
Next.js makes runtime integration easier with something called multi-zones.
Multi-Zones in Next.js
A zone is just a separate Next.js app. Multi-zones allow you to serve multiple Next.js apps under a single domain. For example:
/-> served bywebapp/dashboard-> served bydashboardapp/blog-> served byblogapp
In next.config.js of each app, you define rewrites so that requests are routed to the correct app.
Example next-config from the main app (/):
async rewrites() {
return [
{ source: '/dashboard/:path*', destination: 'http://localhost:3001/:path*' },
{ source: '/blog/:path*', destination: 'http://localhost:3002/:path*' },
]
}The Turborepo Factor
Multi-zones are great, but they don’t share code out of the box. If all your apps need the same UI components, utility functions, or types, duplicating them gets messy.
That’s where Turborepo comes in. It lets you set up a monorepo where multiple apps and shared packages live together. You can have:
/apps
/web
/dashboard
/blog
/packages
/ui
/utilsThen you can import shared code like this:
import { Button } from "@repo/ui/components/ui/button"Implementing Micro-Frontends with Next.js, Multi-Zones, and Turborepo
1. Create a Turborepo
npx create-turbo@latest2. Add your Next.js apps under /apps
npx create-next-app@latest apps/dashboard
npx create-next-app@latest apps/blog3. Set up rewrites in each app for local development
Local:
destination: "http://localhost:3001/:path*"Production:
destination: "https://dashboard.example.com/:path*"4. Extract shared code into /packages/ui
5. Deploy each app independently but under the same domain (with multi-zones)
Architecture Overview
Communication Between Apps
Since each app is independent, sharing state can be tricky. Luckily, because they run on the same domain, you have options:
-
LocalStorage or IndexedDB Works well for persistent data.
-
Cookies Great for authentication and user preferences.
-
Query parameters Useful for passing temporary state.
Example of reading shared auth data via cookies:
import { cookies } from "next/headers"
export async function getUser() {
const token = cookies().get("auth_token")
return token ? verifyToken(token.value) : null
}Deployment Checklist
- Consistent environment variables across apps
- Shared packages properly hoisted in Turborepo
- Routing tested in local and staging environments with
<a>tag not<Link>component - Error boundaries implemented in each app
Recommended Resources
Final Notes
Think of this architecture as a city with multiple districts, each self-governed, but sharing the same postal system. Done well, it makes large frontend systems modular, maintainable, and team-friendly.