Next.js Multi-Zone Architecture in 2026: Splitting a Large App Into Independent Deployments Without Breaking Navigation A developer documented a Next.js multi-zone architecture that splits a 200-route application into three independently built and deployed apps sharing one domain, cutting CI build times from 18 minutes to roughly 4 minutes for the marketing zone and 7 for the dashboard. The approach relies on per-zone assetPrefix settings to prevent static file collisions and rewrites in a coordinating marketing zone to proxy /dashboard/* and /blog/* requests to their respective deployments. The writeup notes the trade-offs: shared code must move to npm packages or submodules, cross-zone navigation triggers full page loads, and routing configuration spans multiple repositories. This article was written with the assistance of AI, under human supervision and review. A 200-route Next.js app with marketing, dashboard, and blog sections takes 18 minutes to build on CI. Developers working on blog pages wait for unrelated dashboard code to compile. A single bug in the checkout flow blocks the entire domain from deploying. Multi-zone architecture splits the app into three separate Next.js applications: one serves / , another serves /dashboard , and a third serves /blog . Each zone lives in its own repository, builds independently, and deploys on its own schedule. The three apps share a single domain, and users navigate between them without knowing the boundary exists. The Next.js multi-zone documentation http://nextjs.org/docs/app/guides/multi-zones describes this approach as separating a large application on a domain into smaller applications that each serve a set of paths. Build times drop to zone size 4 minutes for marketing, 7 for dashboard , teams deploy zones independently, and route ownership boundaries become repository boundaries. Multi-zones trade monolithic simplicity for deployment isolation. Shared code requires npm packages or git submodules, navigation between zones triggers full page loads, and routing configuration spans multiple repositories. The architecture makes sense when deployment independence outweighs coordination cost. assetPrefix and rewrites to route static assets and paths correctly across the shared domain. Multi-zone architecture relies on two mechanisms: assetPrefix prevents static file collisions between zones, and rewrites route incoming requests to the correct zone. Each zone is a normal Next.js application. The marketing zone serves / and /about . The dashboard zone serves /dashboard/ . The blog zone serves /blog/ . All three zones deploy to separate hosts or separate paths on a CDN , but the domain presents them as a unified application. The assetPrefix configuration tells Next.js to prefix all / next/static URLs with a zone-specific path. Without this, two zones would both serve / next/static/chunks/main.js , and the browser would cache the wrong file. The Next.js multi-zone documentation https://nextjs.org/docs/app/building-your-application/deploying/multi-zones shows the pattern for the blog zone: python // apps/blog/next.config.ts import type { NextConfig } from 'next' const nextConfig: NextConfig = { assetPrefix: '/blog-static', async rewrites { return { beforeFiles: { source: '/blog-static/ next/:path+', destination: '/ next/:path+', }, , } }, } export default nextConfig The assetPrefix: '/blog-static' setting changes every emitted HTML src="https://dev.to/ next/static/..." tag to src="https://dev.to/blog-static/ next/static/..." . The rewrite rule maps /blog-static/ next/:path+ back to / next/:path+ so the Next.js server serves files at the original path. This prevents the marketing zone which uses assetPrefix: '/marketing-static' from colliding with the blog zone on static file URLs. Rewrites also route paths to other zones. The marketing zone, which acts as the routing coordinator, adds rewrite rules that proxy /dashboard/ requests to the dashboard zone and /blog/ requests to the blog zone: python // apps/marketing/next.config.ts import type { NextConfig } from 'next' const nextConfig: NextConfig = { assetPrefix: '/marketing-static', async rewrites { return { beforeFiles: { source: '/marketing-static/ next/:path+', destination: '/ next/:path+', }, { source: '/dashboard/:path ', destination: 'https://dashboard.internal.example.com/dashboard/:path ', }, { source: '/dashboard-static/ next/:path+', destination: 'https://dashboard.internal.example.com/ next/:path+', }, { source: '/blog/:path ', destination: 'https://blog.internal.example.com/blog/:path ', }, { source: '/blog-static/ next/:path+', destination: 'https://blog.internal.example.com/ next/:path+', }, , } }, } export default nextConfig The marketing zone serves / directly. When a request arrives for /dashboard/settings , the rewrite rule proxies the request to https://dashboard.internal.example.com/dashboard/settings , which the dashboard zone handles. The browser sees example.com/dashboard/settings in the address bar, but the marketing zone acted as a reverse proxy. Multi-zone routing diagram showing three zones under one domain This setup requires each zone to know about the others only through rewrite rules. The marketing zone knows to proxy /dashboard/ somewhere, but it does not import dashboard code. Each zone builds independently, and the routing layer stitches them together at runtime. A practical multi-zone setup uses a monorepo with three separate Next.js apps. The marketing zone owns the root and coordinates routing. The dashboard zone serves authenticated routes. The blog zone serves content. Create the directory structure: multi-zone-app/ ├── apps/ │ ├── marketing/ │ ├── dashboard/ │ └── blog/ └── package.json Initialize each zone as a standalone Next.js 15 app. The marketing zone uses the default setup: python // apps/marketing/next.config.ts import type { NextConfig } from 'next' const nextConfig: NextConfig = { assetPrefix: '/marketing-static', async rewrites { return { beforeFiles: { source: '/marketing-static/ next/:path+', destination: '/ next/:path+', }, { source: '/dashboard/:path ', destination: process.env.DASHBOARD URL + '/dashboard/:path ', }, { source: '/dashboard-static/ next/:path+', destination: process.env.DASHBOARD URL + '/ next/:path+', }, { source: '/blog/:path ', destination: process.env.BLOG URL + '/blog/:path ', }, { source: '/blog-static/ next/:path+', destination: process.env.BLOG URL + '/ next/:path+', }, , } }, } export default nextConfig The dashboard zone configures basePath and assetPrefix : python // apps/dashboard/next.config.ts import type { NextConfig } from 'next' const nextConfig: NextConfig = { basePath: '/dashboard', assetPrefix: '/dashboard-static', async rewrites { return { beforeFiles: { source: '/dashboard-static/ next/:path+', destination: '/ next/:path+', }, , } }, } export default nextConfig The basePath: '/dashboard' setting tells Next.js that all routes in this zone sit under /dashboard . A page at app/settings/page.tsx serves at /dashboard/settings , not /settings . The blog zone uses the same pattern with basePath: '/blog' . Each zone runs on a different port in development: { "scripts": { "dev:marketing": "cd apps/marketing && next dev -p 3000", "dev:dashboard": "cd apps/dashboard && next dev -p 3001", "dev:blog": "cd apps/blog && next dev -p 3002" } } Set environment variables to point each zone at the others: .env.local in apps/marketing DASHBOARD URL=http://localhost:3001 BLOG URL=http://localhost:3002 Start all three dev servers, then visit http://localhost:3000 . The marketing zone serves / . Clicking a link to /dashboard/settings proxies the request to the dashboard zone. Clicking a link to /blog/post-1 proxies to the blog zone. The browser address bar stays on localhost:3000 for all three zones. Navigation within a zone uses Next.js client-side routing and performs a soft transition without reloading the page. Navigation between zones triggers a hard navigation that unloads the current zone and loads the new one. The Next.js multi-zone documentation http://nextjs.org/docs/app/guides/multi-zones states that navigating from a page in one zone to a page in another zone performs a hard navigation, unloading the resources of the current page and loading the resources of the new page. This means React state, scroll position, and prefetched data do not persist across zone boundaries. Developers often expect