{"slug": "next-js-multi-zone-architecture-in-2026-splitting-a-large-app-into-independent", "title": "Next.js Multi-Zone Architecture in 2026: Splitting a Large App Into Independent Deployments Without Breaking Navigation", "summary": "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.", "body_md": "*This article was written with the assistance of AI, under human supervision and review.*\n\nA 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.\n\nMulti-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.\n\nThe [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.\n\nMulti-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.\n\n`assetPrefix` and `rewrites` to route static assets and paths correctly across the shared domain.\nMulti-zone architecture relies on two mechanisms: `assetPrefix` prevents static file collisions between zones, and `rewrites` route incoming requests to the correct zone.\n\nEach 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.\n\nThe `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:\n\n``` python\n// apps/blog/next.config.ts\nimport type { NextConfig } from 'next'\n\nconst nextConfig: NextConfig = {\n  assetPrefix: '/blog-static',\n  async rewrites() {\n    return {\n      beforeFiles: [\n        {\n          source: '/blog-static/_next/:path+',\n          destination: '/_next/:path+',\n        },\n      ],\n    }\n  },\n}\n\nexport default nextConfig\n```\n\nThe `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.\n\nRewrites 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:\n\n``` python\n// apps/marketing/next.config.ts\nimport type { NextConfig } from 'next'\n\nconst nextConfig: NextConfig = {\n  assetPrefix: '/marketing-static',\n  async rewrites() {\n    return {\n      beforeFiles: [\n        {\n          source: '/marketing-static/_next/:path+',\n          destination: '/_next/:path+',\n        },\n        {\n          source: '/dashboard/:path*',\n          destination: 'https://dashboard.internal.example.com/dashboard/:path*',\n        },\n        {\n          source: '/dashboard-static/_next/:path+',\n          destination: 'https://dashboard.internal.example.com/_next/:path+',\n        },\n        {\n          source: '/blog/:path*',\n          destination: 'https://blog.internal.example.com/blog/:path*',\n        },\n        {\n          source: '/blog-static/_next/:path+',\n          destination: 'https://blog.internal.example.com/_next/:path+',\n        },\n      ],\n    }\n  },\n}\n\nexport default nextConfig\n```\n\nThe 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.\n\n*Multi-zone routing diagram showing three zones under one domain*\n\nThis 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.\n\nA 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.\n\nCreate the directory structure:\n\n```\nmulti-zone-app/\n├── apps/\n│   ├── marketing/\n│   ├── dashboard/\n│   └── blog/\n└── package.json\n```\n\nInitialize each zone as a standalone Next.js 15 app. The marketing zone uses the default setup:\n\n``` python\n// apps/marketing/next.config.ts\nimport type { NextConfig } from 'next'\n\nconst nextConfig: NextConfig = {\n  assetPrefix: '/marketing-static',\n  async rewrites() {\n    return {\n      beforeFiles: [\n        {\n          source: '/marketing-static/_next/:path+',\n          destination: '/_next/:path+',\n        },\n        {\n          source: '/dashboard/:path*',\n          destination: process.env.DASHBOARD_URL + '/dashboard/:path*',\n        },\n        {\n          source: '/dashboard-static/_next/:path+',\n          destination: process.env.DASHBOARD_URL + '/_next/:path+',\n        },\n        {\n          source: '/blog/:path*',\n          destination: process.env.BLOG_URL + '/blog/:path*',\n        },\n        {\n          source: '/blog-static/_next/:path+',\n          destination: process.env.BLOG_URL + '/_next/:path+',\n        },\n      ],\n    }\n  },\n}\n\nexport default nextConfig\n```\n\nThe dashboard zone configures `basePath` and `assetPrefix`:\n\n``` python\n// apps/dashboard/next.config.ts\nimport type { NextConfig } from 'next'\n\nconst nextConfig: NextConfig = {\n  basePath: '/dashboard',\n  assetPrefix: '/dashboard-static',\n  async rewrites() {\n    return {\n      beforeFiles: [\n        {\n          source: '/dashboard-static/_next/:path+',\n          destination: '/_next/:path+',\n        },\n      ],\n    }\n  },\n}\n\nexport default nextConfig\n```\n\nThe `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'`.\n\nEach zone runs on a different port in development:\n\n```\n{\n  \"scripts\": {\n    \"dev:marketing\": \"cd apps/marketing && next dev -p 3000\",\n    \"dev:dashboard\": \"cd apps/dashboard && next dev -p 3001\",\n    \"dev:blog\": \"cd apps/blog && next dev -p 3002\"\n  }\n}\n```\n\nSet environment variables to point each zone at the others:\n\n```\n# .env.local in apps/marketing\nDASHBOARD_URL=http://localhost:3001\nBLOG_URL=http://localhost:3002\n```\n\nStart 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.\n\nNavigation 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.\n\nThe [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.\n\nDevelopers often expect `<Link href=\"https://dev.to/dashboard\">` in the marketing zone to behave like an internal Next.js link. The link works, but the transition is not instant. The browser requests `/dashboard`, the marketing zone rewrites the request to the dashboard zone, and the dashboard zone returns a full HTML document. The marketing zone unloads, and the dashboard zone initializes from scratch.\n\nThis behavior surfaces in two places: initial page load and client navigation. On initial page load, the user requests `example.com/dashboard/settings` directly. The marketing zone rewrites the request, and the dashboard zone serves the HTML. No zone transition occurs because no previous zone was loaded.\n\nOn client navigation, the user starts on `example.com/` (marketing zone) and clicks a link to `/dashboard/settings`. Next.js attempts client-side routing, realizes `/dashboard` is a rewrite destination, and falls back to a full navigation. The browser unloads the marketing zone JavaScript and loads the dashboard zone JavaScript.\n\nTo minimize the disruption, use optimistic UI patterns within each zone. Prefetch critical routes using `<Link prefetch={true}>` and preload zone-specific bundles with `<link rel=\"modulepreload\">`. The zone transition remains a hard navigation, but the next zone loads faster.\n\nFor routes that truly need instant transitions, keep them in the same zone. If `/dashboard` and `/dashboard/settings` must navigate instantly, they belong in the dashboard zone. If `/` and `/about` must navigate instantly, they belong in the marketing zone. Zone boundaries are architectural commitments that trade instant navigation for deployment independence.\n\nMulti-zones, Module Federation, and monorepos solve different coordination problems for large Next.js apps. The right choice depends on whether the app needs runtime code sharing, deployment independence, or coordinated releases.\n\n| Architecture | Deployment model | Code sharing mechanism | Navigation behavior | Best for | \n|---|---|---|---|---|\n| Multi-zones | Independent per zone | npm packages, git submodules | Hard navigation between zones | Clear path ownership, separate teams | \n| Module Federation | Independent per federated module | Runtime chunk loading | Soft navigation, shared runtime | Shared components across deployed apps | \n| Monorepo | Coordinated release | Direct imports via workspace | Soft navigation everywhere | Unified versioning, single deployment unit | \n\nMulti-zones split a domain by path prefix. Each zone deploys independently and owns a set of routes. The marketing zone serves `/`, the dashboard zone serves `/dashboard/*`, and the blog zone serves `/blog/*`. Navigation between zones reloads the page. Shared code lives in npm packages or git submodules, which requires explicit versioning and publishing. This approach works when deployment isolation matters more than code reuse convenience.\n\nModule Federation (covered in [Next.js Turbopack Module Federation 2026](https://jsmanifest.com/nextjs-turbopack-module-federation-2026)) loads remote modules at runtime. The marketing app can import a dashboard component without bundling it. The browser fetches the component from the dashboard deployment at runtime. Navigation stays within a single JavaScript runtime, so transitions are instant and state persists. Module Federation works when apps need to share UI components but deploy independently. The coordination cost shifts from versioning npm packages to managing runtime contracts.\n\nMonorepos bundle all apps into one build and deploy them together. Shared code lives in workspace packages that apps import directly. Navigation uses Next.js client-side routing everywhere, and the entire domain is a single deployment unit. Monorepos work when teams coordinate releases and value simplified dependency management over deployment independence.\n\nThe failure mode for multi-zones is excessive coordination overhead. Teams spend time publishing shared packages, syncing API contracts, and debugging rewrite rules. The failure mode for Module Federation is runtime dependency conflicts. Two zones load incompatible React versions, and the app crashes. The failure mode for monorepos is deployment coupling. A bug in one team's code blocks the entire domain from deploying.\n\nFor a 500-route app split across marketing, product, and content, multi-zones reduce build times from 20 minutes to 5 minutes per zone. Deployments stay independent, and teams own their release schedule. For a 50-route app with heavy component reuse, a monorepo keeps imports simple and navigation instant. For apps that need both, Module Federation shares components without coupling deployments, but adds runtime complexity.\n\n*Shared component architecture diagram showing npm packages consumed by multiple zones*\n\nShared components in multi-zone setups live in npm packages or workspace libraries. Each zone imports the package and bundles it independently. The marketing zone and dashboard zone can both use a `<Button>` component from `@company/ui`, but they bundle separate copies of the code.\n\nThis duplication increases total bundle size but keeps zones independent. The marketing zone can upgrade `@company/ui` to version 2.0 without waiting for the dashboard zone. The tradeoff is cache inefficiency: the browser downloads the same `Button` code twice because it sits in different JavaScript chunks.\n\nTo share components without npm packages, use a monorepo tool like Turborepo or Nx. Create a `packages/ui` directory, and configure each zone to import from it:\n\n``` js\n// apps/marketing/app/page.tsx\nimport { Button } from '@company/ui/button'\n\nexport default function Home() {\n  return <Button>Click me</Button>\n}\n```\n\nThe `@company/ui` import resolves to `packages/ui` via TypeScript path aliases. Each zone bundles the imported code independently, but developers edit a single source file. Versioning happens implicitly: all zones see the same code at build time.\n\nAuthentication across zones requires sharing session tokens or cookies. The marketing zone sets a `session` cookie at `example.com`, which the dashboard zone reads. Both zones share the same cookie domain, so the token persists across zone boundaries.\n\n``` js\n// apps/dashboard/middleware.ts\nimport { NextRequest, NextResponse } from 'next/server'\n\nexport function middleware(request: NextRequest) {\n  const session = request.cookies.get('session')\n\n  if (!session) {\n    return NextResponse.redirect(new URL('/', request.url))\n  }\n\n  return NextResponse.next()\n}\n\nexport const config = {\n  matcher: '/dashboard/:path*',\n}\n```\n\nThe dashboard zone middleware reads the `session` cookie and redirects unauthenticated users to `/`, which the marketing zone serves. The marketing zone login flow sets the cookie, and all zones can read it.\n\nEnvironment variables stay zone-specific. The dashboard zone has `DASHBOARD_API_URL`, and the blog zone has `BLOG_CMS_URL`. Shared values like `DATABASE_URL` must be duplicated across zone configurations. Use a tool like Doppler or AWS Secrets Manager to sync secrets across zones, or set them at the reverse proxy layer.\n\nFor runtime feature flags, use a service like LaunchDarkly or split.io that both zones query. Each zone evaluates flags independently, which allows gradual rollouts across zones. The marketing zone can enable a new homepage while the dashboard zone uses the old layout.\n\nMulti-zone deployments require a reverse proxy that routes paths to the correct zone. Vercel, AWS Amplify, and self-hosted nginx or Caddy all support this pattern with different configuration models.\n\nOn Vercel, deploy each zone as a separate project. The marketing zone uses the production domain `example.com`. The dashboard and blog zones use internal domains like `dashboard-internal.vercel.app` and `blog-internal.vercel.app`. The marketing zone `next.config.ts` rewrites `/dashboard/*` and `/blog/*` to the internal domains, as shown in earlier sections.\n\nVercel handles HTTPS termination, caching, and routing automatically. The marketing zone deployment acts as the reverse proxy. Developers deploy each zone independently by pushing to separate Git branches or repositories.\n\nOn AWS Amplify, deploy each zone as a separate Amplify app. Create a CloudFront distribution that routes paths to the correct app origin. The CloudFront behavior rules map `/dashboard/*` to the dashboard app and `/blog/*` to the blog app:\n\n```\n{\n  \"pathPattern\": \"/dashboard/*\",\n  \"targetOriginId\": \"dashboard-app\",\n  \"viewerProtocolPolicy\": \"redirect-to-https\"\n}\n```\n\nThe marketing app serves as the default origin for `/`. CloudFront evaluates behavior rules in order and routes requests to the matching origin. Each Amplify app builds and deploys independently, and CloudFront stitches them together at the edge.\n\nFor self-hosted deployments, use nginx or Caddy as the reverse proxy. Each zone deploys to a separate server or container. The nginx configuration proxies paths to upstream servers:\n\n```\nserver {\n  listen 80;\n  server_name example.com;\n\n  location /dashboard/ {\n    proxy_pass http://dashboard-zone:3001;\n    proxy_set_header Host $host;\n    proxy_set_header X-Real-IP $remote_addr;\n  }\n\n  location /blog/ {\n    proxy_pass http://blog-zone:3002;\n    proxy_set_header Host $host;\n    proxy_set_header X-Real-IP $remote_addr;\n  }\n\n  location / {\n    proxy_pass http://marketing-zone:3000;\n    proxy_set_header Host $host;\n    proxy_set_header X-Real-IP $remote_addr;\n  }\n}\n```\n\nEach zone runs behind the proxy, which handles HTTPS termination and routing. Developers deploy each zone by updating the container image or restarting the upstream server. The proxy configuration stays static.\n\nFor global deployments, deploy each zone to multiple regions and use a global load balancer (AWS Global Accelerator, Cloudflare Load Balancing) to route traffic to the nearest region. Each region runs a complete set of zones, and the load balancer routes by geography and health checks.\n\nThe deployment strategy depends on the infrastructure: Vercel simplifies multi-zone setup with built-in rewrites, AWS Amplify requires CloudFront configuration, and self-hosted setups give full control over routing at the cost of managing the reverse proxy.\n\nMulti-zones add coordination overhead that exceeds their value when zones share too much code, when teams deploy together anyway, or when navigation performance matters more than deployment independence.\n\nA multi-zone setup with 80% shared code between zones means 80% of changes require synchronized releases. Teams publish shared packages, update dependency versions across zones, and test integration points. The deployment independence benefit disappears because zones cannot deploy without coordinating. At this point, a monorepo with workspace packages removes the versioning overhead and keeps navigation instant.\n\nMulti-zones also fail when zones rarely deploy independently. If marketing, dashboard, and blog always release together, the architecture adds complexity without delivering its core benefit. Teams maintain separate repositories, rewrite rules, and npm packages, but deployments stay synchronized. A monorepo with a unified CI/CD pipeline removes the coordination cost.\n\nWhen navigation performance matters more than deployment isolation, multi-zones introduce a permanent UX regression. Every cross-zone link triggers a hard navigation that reloads the page. Users notice the delay, especially on slower connections. Server Components and React Server Components patterns (covered in [React Server Components Patterns and Pitfalls 2026](https://jsmanifest.com/react-server-components-patterns-pitfalls-2026)) can optimize loading, but the fundamental navigation model stays the same. If instant navigation across all routes is non-negotiable, keep the routes in a single zone or migrate to Module Federation.\n\nThe migration path from multi-zones back to a monolith depends on the reason for consolidation. If shared code is the problem, move zones into a monorepo but keep them as separate Next.js apps. The coordination cost drops because teams work in one repository, but zones still deploy independently if needed. If deployment coupling is acceptable, merge the zones into a single Next.js app with route groups. Navigation becomes instant, and the deployment unit shrinks to one.\n\nTo migrate incrementally, move one zone at a time. Merge the blog zone into the marketing zone by copying routes from `apps/blog/app/blog` to `apps/marketing/app/blog`. Update the marketing zone to remove the blog rewrite rule. Deploy the updated marketing zone, which now serves `/blog/*` directly. Repeat for the dashboard zone. Each step reduces the number of independent deployments until the entire domain lives in one app.\n\nAlternative patterns include parallel routes (covered in [Next.js Parallel Routes for Complex Layouts](https://jsmanifest.com/nextjs-parallel-routes-complex-layouts)) for rendering multiple zones simultaneously on one page, and edge middleware for routing decisions at the CDN layer. Both approaches solve different problems than deployment independence.\n\nYes, but only if the versions share the same runtime contract. A Next.js 14 zone and a Next.js 15 zone can coexist under one domain, but shared cookies, headers, and client-side routing assumptions must stay compatible. Test zone boundaries carefully when upgrading one zone ahead of others.\n\nCheck the browser network tab for rewrite destinations. The marketing zone should proxy `/dashboard/*` requests to the dashboard zone URL. If the request returns 404, the rewrite rule or destination URL is incorrect. Verify `assetPrefix` matches between the zone config and the rewrite rule that proxies static assets.\n\nSearch engines see a single domain with normal HTML responses. Each zone renders server-side, so crawlers index pages correctly. The hard navigation between zones does not affect SEO because crawlers follow links as separate requests. Use canonical URLs and sitemaps across all zones to ensure consistent indexing.\n\nNo. React context exists within a single JavaScript runtime. Crossing a zone boundary unloads the current runtime and loads a new one. Shared state must live in cookies, localStorage, or an external service that both zones query.\n\nEach zone defines its own `not-found.tsx` file. If the marketing zone receives a request for `/unknown`, it shows the marketing zone 404 page. If the dashboard zone receives a request for `/dashboard/unknown`, it shows the dashboard zone 404 page. For a unified 404 experience, use the same component in each zone's `not-found.tsx` file.\n\nMulti-zone architecture splits a Next.js domain into independent applications that deploy separately, route transparently, and share a domain. Teams gain deployment isolation, parallel workflows, and reduced build times. The cost is hard navigation between zones, shared component versioning, and rewrite configuration across repositories. The pattern works when deployment independence justifies coordination overhead, typically for large apps with clear ownership boundaries and teams that release on different schedules. For apps with heavy code sharing or strict navigation performance requirements, monorepos or Module Federation offer different tradeoffs.", "url": "https://wpnews.pro/news/next-js-multi-zone-architecture-in-2026-splitting-a-large-app-into-independent", "canonical_source": "https://dev.to/jsmanifest/nextjs-multi-zone-architecture-in-2026-splitting-a-large-app-into-independent-deployments-without-2ln1", "published_at": "2026-11-12 06:06:42+00:00", "updated_at": "2026-11-12 06:21:22.822385+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["Next.js", "Vercel"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/next-js-multi-zone-architecture-in-2026-splitting-a-large-app-into-independent", "markdown": "https://wpnews.pro/news/next-js-multi-zone-architecture-in-2026-splitting-a-large-app-into-independent.md", "text": "https://wpnews.pro/news/next-js-multi-zone-architecture-in-2026-splitting-a-large-app-into-independent.txt", "jsonld": "https://wpnews.pro/news/next-js-multi-zone-architecture-in-2026-splitting-a-large-app-into-independent.jsonld"}}