Fixing 404s on Routes That Should Exist in the App Router

A route that looks correct on disk can still 404. Walk through the exact causes: a missing page file, a route group conflict, and a few other common ones.

7 min read

A next.js 404 on a route that exists on disk and looks correct is one of the more confusing App Router problems to debug, because the file tree offers no obvious clue. This walks through the specific causes worth checking, in the order they are worth ruling out.

No page.tsx in the folder

The most common cause is also the simplest: the folder exists, but nothing inside it makes it public. A route only becomes reachable once it contains a page file or a route file, as covered in how file-based routing works in the Next.js App Router.

texttext
app/
  dashboard/
    settings/
      layout.tsx   -> exists, but adds no page

Visiting /dashboard/settings 404s here because a layout file alone never makes a segment public. Add a page file next to it to fix this.

dynamicParams set to false

If a dynamic route exports generateStaticParams, any value that function did not return still renders normally by default. Setting dynamicParams to false changes that, and any value outside the returned list becomes a real 404 instead.

typescripttypescript
// app/blog/[slug]/page.tsx
export const dynamicParams = false;

A slug that exists in the underlying data source, but was left out of the returned array, 404s here even though the page's own logic never rejected it and the surrounding data fetch works exactly as expected. Removing this line, or adding the missing value to the returned list, fixes it. The full return shape for that function is covered in generateStaticParams explained with real examples.

A route group path collision

Two different route groups resolving to the same URL fail the entire build, not just the one route that looks wrong.

texttext
app/(marketing)/about/page.tsx
app/(shop)/about/page.tsx

Both resolve to /about, and Next.js rejects the build with a conflicting path error rather than picking one silently. Rename or move one of the two pages so only one folder produces that path. This one is easier to rule out than most causes on this list, since a genuine collision surfaces immediately in the build log rather than showing up later as a mysterious 404 in production.

A private folder hiding the segment

A folder prefixed with an underscore opts itself, and everything nested inside it, out of routing entirely. A page.tsx accidentally placed inside a private folder never becomes reachable, no matter how correctly it is written.

texttext
app/
  _blog/
    page.tsx   -> never routable, the parent folder is private

Renaming the folder to drop the underscore prefix restores normal routing for everything inside it. This particular cause tends to surface after someone copies a folder structure from another project or an example repository without checking whether the underscore convention was intentional there, or already in active use for a different purpose.

Missing default.js in a parallel route slot

For the implicit children slot in a layout using parallel routes, a missing default.tsx produces a 404 for the whole route on a hard navigation, even though the page itself renders correctly on a normal client-side click. Adding one, even a minimal file that returns null, resolves it. Named slots behave differently and are covered in default.js explained.

Checklist

SymptomLikely cause
Folder exists, route still 404sMissing page.tsx or route.ts
Only some dynamic values 404dynamicParams is false
Build fails instead of just one routeRoute group path collision
Nothing under a folder ever routesPrivate, underscore-prefixed folder

Work through this list in order before assuming a data or code bug, since routing configuration causes far more unexpected 404s than actual application logic does. Checking the file tree first also tends to be faster than adding logging or stepping through a debugger, because most of these causes are visible the moment you look at the right folder with the right expectation in mind.

Rune AI

Rune AI

Key Insights

  • A folder needs a page.tsx or route.ts file to be reachable at all.
  • dynamicParams set to false 404s any value generateStaticParams did not return.
  • Two route groups resolving to the same path fail the build, not just one route.
  • A private, underscore-prefixed folder is invisible to routing entirely.
  • A missing default.js 404s only the implicit children slot in a parallel route.
RunePowered by Rune AI

Frequently Asked Questions

Does a missing default.js in a parallel route cause a 404?

For the implicit children slot, yes. For a named slot like @analytics, a missing default file now throws a build or dev error instead, unless it explicitly calls notFound() to opt back into a 404.

Can a typo in a dynamic folder name cause this?

Yes. A folder named [slugs] instead of [slug] still routes correctly on its own, but the params key it produces won't match what the page destructures, which can look like a 404 if the page then calls notFound() itself on the missing value.

Is dynamicParams the same as generateStaticParams?

No. generateStaticParams supplies the values to prerender at build time. dynamicParams controls what happens to a value outside that list, and setting it to false turns any unlisted value into a real 404.

Conclusion

An unexpected 404 in the App Router almost always traces back to one of a small number of causes: a missing page file, a dynamicParams override, a route group path collision, or a private folder hiding the segment entirely. Check the file tree before assuming the code is wrong.