How to Fix: Using a parallel route slot with dynamic route, in parallel to a catch-all route causes non-turbo dev server and build to fail.
Next.js App Router build failure with a dynamic parallel route beside a catch-all route: why it breaks and how to fix it
This bug appears when a parallel route slot contains a dynamic segment while a sibling branch uses a catch-all route. In affected Next.js 14.2.x setups, the route tree becomes ambiguous during route resolution, which can crash the non-Turbo dev server and fail production builds.
What the problem looks like
You typically hit this issue in an App Router project with a structure similar to this:
app/
[lang]/
@slot/
[dynamic]/
page.tsx
[...catchAll]/
page.tsx
Or any equivalent layout where:
- one branch uses a parallel route like
@modal,@slot, or similar, - that slot contains a dynamic segment such as
[id], - another branch at the same routing level uses a catch-all segment like
[...slug]or[[...slug]].
Symptoms usually include:
- next dev failing when running the non-Turbo dev server,
- next build failing during route analysis,
- confusing errors that suggest conflicting route definitions even though the folder structure looks valid.
If you are reproducing from the issue repository, inspect the route tree first and identify the exact point where the slot route and the catch-all route coexist.
Understanding the Root Cause
In the Next.js App Router, the file-system router compiles your folders into an internal segment tree. Parallel routes are not normal URL path segments. A folder like @slot is a named rendering channel attached to the same parent layout, while a folder like [id] or [...slug] contributes matching logic for URL resolution.
The problem happens because a dynamic segment inside a parallel slot can still participate in route tree normalization, while a sibling catch-all route claims a broad match space at that same level. During build-time analysis, Next.js attempts to determine which route branch is authoritative for a given pathname. In affected versions, the resolver can incorrectly treat these branches as conflicting or generate an invalid traversal order.
Technically, the failure is caused by a mismatch between:
- how parallel route slots are represented in the route manifest, and
- how catch-all dynamic segments are ranked and merged.
Because a catch-all route is intentionally greedy, the compiler must guarantee that all sibling segments are unambiguous. When a slot branch also introduces a dynamic matcher, the route analyzer in these versions may not fully isolate slot semantics from path semantics. That leads to:
- incorrect route conflict detection,
- manifest generation errors,
- dev server crashes,
- build failures in static analysis.
So the key takeaway is this: the bug is not your syntax alone. It is an implementation edge case in Next.js route-tree compilation when parallel slots, dynamic segments, and catch-all segments intersect at the same hierarchy boundary.
Step-by-Step Solution
The most reliable fix is to remove the routing ambiguity by ensuring that the dynamic path segment and the catch-all segment do not compete at the same level.
1. Identify the conflicting structure
Look for a directory layout resembling this:
app/
section/
@preview/
[slug]/
page.tsx
[...slug]/
page.tsx
This is the risky pattern: a dynamic route under a parallel slot beside a catch-all route.
2. Move the dynamic segment out of the slot branch, or add a static boundary
Instead of allowing both branches to resolve broad dynamic matches at the same level, introduce a static segment so the route tree becomes deterministic.
Problematic version:
app/
dashboard/
@modal/
[id]/
page.tsx
[...slug]/
page.tsx
Safer version:
app/
dashboard/
@modal/
item/
[id]/
page.tsx
[...slug]/
page.tsx
By inserting item, the slot branch no longer competes with the catch-all branch for the same abstract match space.
3. Alternative fix: move the catch-all route deeper
If your URL design allows it, relocate the catch-all route under its own static namespace.
app/
dashboard/
@modal/
[id]/
page.tsx
docs/
[...slug]/
page.tsx
This works because the catch-all now only applies under /dashboard/docs/*, instead of colliding with all siblings.
4. Use one source of dynamic matching per routing level
If the slot content is purely UI composition and does not need direct URL matching, remove the dynamic segment from the slot branch and pass data through props or parent layout state.
app/
dashboard/
layout.tsx
page.tsx
@modal/
default.tsx
[...slug]/
page.tsx
Example layout:
export default function DashboardLayout({
children,
modal,
}: {
children: React.ReactNode
modal: React.ReactNode
}) {
return (
<>
{children}
{modal}
</>
)
}
Then render slot state from a route that does not introduce a sibling dynamic conflict.
5. If possible, upgrade Next.js
This issue is tied to framework internals, so the cleanest long-term fix is often a version upgrade once a patch lands. Check the official Next.js issue tracker and release notes for a fix related to parallel routes and catch-all segments.
If upgrading is not currently possible, keep the workaround in place and avoid route trees where a slot-local dynamic segment and a sibling catch-all exist side by side.
6. Verify with both dev and build commands
npm run dev
npm run build
Do not stop after the dev server starts. This bug often appears differently between development route resolution and production build analysis, so both commands must pass.
7. Recommended refactor pattern
If you need both behaviors, use this design rule:
- Parallel slot for rendering composition
- Static boundary before slot-local dynamic segments
- Catch-all route under its own namespace whenever possible
app/
shop/
layout.tsx
@overlay/
product/
[id]/
page.tsx
browse/
[...slug]/
page.tsx
This preserves advanced routing without triggering the resolver bug.
Common Edge Cases
Optional catch-all routes
[[...slug]] can be even trickier than [...slug] because it also matches the empty path. That broadens ambiguity further. If you use an optional catch-all, separating it with a static prefix becomes even more important.
Intercepting routes combined with slots
If your app also uses intercepting routes such as (.) or (..), route resolution becomes more complex. Keep intercepting routes isolated from branches that already contain catch-all matching.
Static export assumptions
If you expect a route to be statically generated, a sibling catch-all plus slot-level dynamics can interfere with static analysis. Even after refactoring, confirm whether you need generateStaticParams or a dynamic rendering mode.
Duplicate params with different names
Even if segment names differ, such as [id] in one branch and [...slug] in another, the issue is not the variable name. The issue is their overlapping match scope.
Layouts hiding the real conflict
Sometimes the problem is spread across route groups and layouts, so the collision is not obvious from one folder. Expand the full path hierarchy and evaluate all sibling branches at the same routing level.
FAQ
1. Why does the route work conceptually but still fail in Next.js?
Because the failure happens in framework route compilation, not just URL theory. The route shape may make sense to a developer, but the affected Next.js version cannot safely normalize that combination of parallel slots and catch-all matching.
2. Can I keep both the parallel route and the catch-all route?
Yes, but usually not at the same ambiguous hierarchy level. Add a static segment boundary or move one branch deeper so the resolver can distinguish them deterministically.
3. Is this only a dev server issue?
No. The issue commonly affects both the non-Turbo dev server and next build. Always test both, because build-time route analysis is often stricter.
The practical fix is simple: do not let a dynamic segment inside a parallel slot compete directly with a sibling catch-all route. Add a static namespace, move one branch deeper, or restructure the slot so it does not own URL matching at that level.