Type something to search...
Next.js default.js

Next.js default.js

If you've never touched Parallel Routes, default.js is one of those files you'll never think about — and the moment you do touch Parallel Routes, you'll hit a hard error that only default.js can fix. It's a small file with a narrow job, but skipping it turns an otherwise working layout into a 404 factory the instant someone refreshes the page. This article explains what default.js actually does, why Next.js forces you to write it, and how to use it correctly.

A Quick Refresher on Parallel Routes

Before default.js makes any sense, you need the shape of the problem it solves. Parallel Routes let you render more than one independent page inside the same layout, using named "slots." A slot is a folder prefixed with @, and each slot renders its own page based on the current URL, side by side with the others.

app/
  layout.tsx
  @team/
    page.tsx
    settings/
      page.tsx
  @analytics/
    page.tsx
  page.tsx

Here, @team and @analytics are two slots rendered together by the root layout, alongside the implicit children slot for the main page. Visiting /settings renders the settings subpage inside @team, while @analytics keeps showing whatever it was last showing. Two independently addressable regions of the same screen, both driven by routing.

That independence is the whole point of Parallel Routes — and it's also exactly what breaks on a hard reload.

The Problem: Slots Don't Know What to Render

When you navigate client-side (a "soft" navigation — clicking a <Link>, calling router.push), Next.js keeps track of which subpage each slot was last showing. So if you're on /settings and @analytics is showing its default view, and you then click a link that changes @team to something else, Next.js remembers to keep rendering @analytics's current subpage even though the URL doesn't mention it at all.

A full-page load throws that memory away. There's no client-side router state to fall back on — Next.js has to render something for @analytics at /settings, and the URL itself gives it zero information about what that something should be, because /settings was never a URL that @analytics understood in the first place.

This is the exact scenario default.js exists for: rendering a slot when the current URL doesn't match any of that slot's own subpages.

Without a default.js file, Next.js has no fallback to reach for, and it throws an error for named slots at build/runtime rather than silently guessing. You're required to make an explicit decision about what an unmatched slot should show.

Writing a Basic default.js

The simplest default.js renders nothing, or renders a lightweight placeholder:

// app/@analytics/default.js
export default function Default() {
  return null;
}

Returning null is a completely valid, common choice — it just means "there's nothing to show here for this URL," and the rest of the page renders normally around it.

If you'd rather preserve the pre-Parallel-Routes behavior of a hard 404 when a slot has nothing to show, you can do that explicitly instead:

// app/@team/default.js
import { notFound } from "next/navigation";

export default function Default() {
  notFound();
}

That distinction matters: Next.js won't assume a 404 is what you want anymore. Silence (null) and a hard 404 (notFound()) are both one-line decisions you now have to make consciously, per slot.

Don't Forget the Implicit children Slot

This is the part people miss. Every route with Parallel Routes has an implicit slot called children — it's what renders the regular page.tsx alongside your named slots, and it behaves exactly like any other slot for this purpose.

If you only add default.js to your named slots (@team, @analytics) and skip it for children, you'll get a 404 for the main content on any hard navigation Next.js can't resolve — even though your named slots render fine. This is a genuinely easy thing to overlook, because children doesn't look like a slot in your folder structure the way @team does; it's just wherever page.tsx lives.

The fix is the same shape:

// app/default.js
export default function Default() {
  return null;
}

A working Parallel Routes layout with two slots typically ends up needing three default.js files, not two — one per named slot, plus one at the level where children is implicit.

Accessing params in default.js

Like page.js and layout.js, default.js can receive a params prop — a promise resolving to the dynamic segments from the root down to that slot's subpages.

// app/[artist]/@sidebar/default.js
export default async function Default({
  params,
}: {
  params: Promise<{ artist: string }>;
}) {
  const { artist } = await params;
  return <SidebarFallback artist={artist} />;
}
RouteURLparams
app/[artist]/@sidebar/default.js/zackPromise<{ artist: 'zack' }>
app/[artist]/[album]/@sidebar/default.js/zack/nextPromise<{ artist: 'zack', album: 'next' }>

params is a promise, so it has to be awaited (or unwrapped with React's use()) — you can't destructure it synchronously the way older Next.js versions allowed. This is worth using deliberately: even a fallback slot can render something contextual (like "no analytics data for this artist yet") rather than a completely generic placeholder, since it still has access to the same route params the active page does.

A Practical Example: Dashboard With Optional Analytics

Here's a slightly fuller version of the layout/slot structure that makes the need for default.js concrete:

// app/layout.tsx
export default function Layout({
  children,
  team,
  analytics,
}: {
  children: React.ReactNode;
  team: React.ReactNode;
  analytics: React.ReactNode;
}) {
  return (
    <div className="dashboard">
      <main>{children}</main>
      <aside>{team}</aside>
      <aside>{analytics}</aside>
    </div>
  );
}
app/
  layout.tsx
  page.tsx            → children (main dashboard)
  default.js           → fallback for children
  @team/
    page.tsx           → /team
    settings/
      page.tsx          → /team/settings
    default.js          → fallback for @team on any other URL
  @analytics/
    page.tsx            → /analytics
    default.js           → fallback for @analytics on any other URL

Navigate to /team/settings via a client-side link from the dashboard, and @analytics keeps rendering whatever it last showed — no default.js involved at all, because the soft-navigation state is intact. Refresh the browser on /team/settings, though, and Next.js has no client state to fall back on: @analytics/default.js and the root default.js both fire, because neither @analytics nor children (the main dashboard page) has a subpage that matches /team/settings.

This is why default.js tends to surface as a bug during QA rather than during development — hot-reloading a dev server rarely triggers a genuine hard navigation the way a real user hitting refresh, sharing a deep link, or arriving from an external site does.

Common Mistakes

Forgetting default.js entirely and only discovering it in production. Local dev with fast refresh often doesn't reproduce the hard-navigation path that exposes the missing file. Test by hard-refreshing (not soft-navigating) on a route where a slot has an active subpage.

Assuming default.js and not-found.js are interchangeable. They aren't — not-found.js handles the notFound() function and unmatched routes generally, while default.js handles this specific slot-fallback case for Parallel Routes. You can call notFound() from inside a default.js, but that's a choice you're making, not something Next.js does automatically.

Missing the children slot. As covered above, this is the single most common oversight — every named slot gets attention, and the implicit children slot gets forgotten because it isn't visually a folder with an @ in its name.

Returning null when the user actually needs feedback. For an analytics widget, "nothing" is often fine. For something the user expects to always see, silently rendering nothing on refresh can look like a broken page rather than a working fallback — sometimes a lightweight loading or "not applicable here" message is the better default (pun intended).

Key Takeaways

QuestionAnswer
When does default.js render?When a hard navigation lands on a URL a slot's own subpages don't match
Does it matter for soft (client-side) navigation?No — Next.js preserves slot state during soft navigation, so default.js never fires there
Do I need one for every slot?Yes, including the implicit children slot, or you'll get 404s on hard navigation
Can I return a 404 instead of a fallback?Yes — call notFound() inside default.js to opt back into that behavior
What props does it receive?The same params promise a page.js at that level would receive
Is default.js used outside Parallel Routes?No — it only exists to solve this one Parallel Routes problem

default.js is a small, single-purpose file, but it's not optional the moment you adopt Parallel Routes with more than one slot. Treat it as part of the checklist any time you add a new slot: one page.js for the matched case, one default.js for everything else.

Tags :
Share :

Related Posts

Can Next.js Be Used with GraphQL?

Can Next.js Be Used with GraphQL?

Next.js and GraphQL are two powerful technologies that have gained significant traction in the web development community. Next.js, a React-based fram

Dive Deeper
How does Next.js differ from Create React App?

How does Next.js differ from Create React App?

In the world of modern web development, React.js has emerged as a dominant force due to its flexibility, performance, and extensive ecosystem. Two po

Dive Deeper
How does Next.js handle image optimization?

How does Next.js handle image optimization?

In modern web development, image optimization plays a critical role in enhancing user experience and improving site performance. Large, unoptimized i

Dive Deeper