The Problem
I shipped a Next.js 16 build to Vercel last Wednesday and the homepage 404'd in production. Same routes worked perfectly in next dev for the past week. The culprit was a parallel route I had added a week earlier. It was a @modal slot rendering an auth dialog over the home page, and it took down /, /about, and every other page that did not explicitly open the modal.
If you are hitting 404 - This page could not be found only on routes that use parallel routes (@something folders), only after a production build, and only on certain navigation paths, the cause is one of three things: a missing default.tsx, a route group eating your slot's segment, or async params not getting awaited inside a slot's page.
Why It Happens
Parallel routes work by rendering multiple page trees into named slots in a layout. In dev, Next.js streams aggressively and forgives a missing slot. The layout gets a null slot prop and renders without error. In a production build with Static Site Generation enabled (the default in 16.2), the renderer requires every slot to resolve to a real component on every reachable route. If it cannot, the page is removed from the prerender manifest and a hard 404 ships.
Missing default.tsx. When a user navigates from / to /login and you have a @modal slot in the root layout, Next.js needs to know what to render in @modal for /login. If app/@modal/default.tsx does not exist, the build emits the route but the runtime returns notFound() because the slot tree is incomplete. In dev this gets papered over with a placeholder. In production it 404s.
Route groups swallowing the segment. A folder like app/(marketing)/page.tsx is a route group. The () part does not appear in the URL. If you put a parallel slot inside the wrong group, like app/(marketing)/@modal/default.tsx, the slot is scoped to (marketing) only, so /dashboard (a different group) renders without that slot at all. The build fails the prerender check for /dashboard and ships a 404.
Async params not awaited. Next.js 16 made params and searchParams async by default. Inside a parallel slot, params come from the same async boundary. If your slot's page.tsx reads params.id synchronously, the build collects that page as failed-to-render and excludes it from the manifest. The parent route then 404s because the slot is unresolvable.
The Fix
Step 1: Reproduce the 404 with next build && next start. Always test parallel routes against a real build, not just dev. The error message in next start is more specific than what you see on Vercel:
next build
next start
# visit each route that uses the parallel slot
Look for Error: NEXT_NOT_FOUND in the build output. The line above it tells you which slot could not render and on which route.
Step 2: Add default.tsx to every slot at every level. This is non-negotiable. For a @modal slot at the app root:
// app/@modal/default.tsx
export default function ModalDefault() {
return null
}
Returning null is intentional. The slot exists, it just renders nothing on routes that do not open the modal. Do this for every slot folder, including nested ones:
app/
layout.tsx
page.tsx
@modal/
default.tsx // required
(.)login/
page.tsx
default.tsx // required if this folder has children
dashboard/
layout.tsx
page.tsx
@sidebar/
default.tsx // required
page.tsx
If you have intercepting routes like (.)login, the default.tsx inside that folder is also required because the intercept can be bypassed via direct navigation.
Step 3: Fix route group scope. A parallel slot lives at the same folder level as the layout that consumes it. If your root app/layout.tsx declares <>{children}{modal}</>, the slot folder @modal must sit at app/@modal/, not inside any group:
app/
layout.tsx // consumes {modal}
@modal/ // correct: at root level
(marketing)/
@modal/ // WRONG: scoped to (marketing) only
If you need the slot scoped to a section, make sure the layout that consumes it sits in the same group:
app/
(app)/
layout.tsx // consumes {modal}
@modal/ // correct: at (app) level
page.tsx
Step 4: Await params in the slot's page. In Next.js 16 this is mandatory for both the main page and any slot pages:
// app/@modal/(.)product/[id]/page.tsx
type Props = { params: Promise<{ id: string }> }
export default async function ProductModal({ params }: Props) {
const { id } = await params
return <Modal productId={id} />
}
Forgetting await produces a TypeError in dev but a build-time prerender failure in production, which is what triggers the 404 on the parent route.
Step 5: Verify with the route manifest. After next build, open .next/routes-manifest.json and grep for your route. If it is not in staticRoutes or dynamicRoutes, it did not ship and a hard 404 will be returned by Vercel's edge. The Next.js parallel routes docs cover the prerender contract in detail.
The Lesson
Parallel routes feel forgiving in dev because Next.js streams around missing slots. Production has zero tolerance — every slot must resolve on every reachable route, with default.tsx files at every level and async params correctly awaited. Always run a real build before deploying any change to a parallel route, even if it is "just adding a folder."
If you are stuck on a Vercel 404 right now and need someone to unwind it, see my services, or if you hit a related Next.js 16 issue I wrote up the Next.js async params TypeError fix recently.