Verified against Claude Code · 2026-07-26
Build a multi-panel dashboard with parallel routes instead of client-side tab state
Structures independent dashboard panels as parallel route slots that each stream, load, and error on their own, with the default.tsx behavior on hard versus soft navigation spelled out explicitly.
The prompt
Ready to copy — highlighted parts are example details you can swap.
You are structuring a multi-panel dashboard using parallel routes, the @slotName folder convention, under a shared layout.tsx, instead of one page component that fetches everything up front and manages an "active panel" with client-side tab state. DASHBOARD PANELS a revenue trend chart (last 90 days), a support-ticket queue list, and a team-utilization heatmap NAVIGATION REQUIREMENTS The support-ticket queue needs its own filter state reflected in the URL and shareable via link; the other two panels don't need deep-linking CURRENT IMPLEMENTATION One DashboardPage component fetches all three datasets in parallel with Promise.all, then renders all three children behind a single shared loading boolean — nothing is actually gated by tab state, but one slow query still blocks the whole page. ROUTE BASE /dashboard REQUIREMENTS Create one @slotName folder per independent panel directly under the shared layout, each with its own page.tsx that fetches only that panel's data — never a parent component that fetches everything up front and distributes it down as props to children that could have fetched it themselves. Give each slot its own loading.tsx so a slow panel shows its own skeleton and streams in on its own schedule; a slow analytics query must never block a fast activity feed from rendering just because they happen to share a layout. Give each slot its own error.tsx too, so a failure in one panel's data fetch renders an inline error scoped to that panel, not a full-page crash that takes every other panel down with it. Add a default.tsx to every slot, returning either null or a sensible fallback view. This matters for two distinct reasons: on a hard navigation or full page reload, where the current URL only matches one slot's most specific route, Next.js needs default.tsx to know what to render for every other slot that has no matching segment in that URL — without it, that mismatch produces a 404 for the entire layout, not just for the one unmatched slot. On a soft, client-side navigation, Next.js instead preserves whichever content an unmatched slot was already showing rather than falling back to default.tsx at all, which is a real behavioral difference worth confirming your panels actually handle correctly rather than assuming either mode covers both cases. The layout.tsx itself receives all slots as named props alongside children and is responsible only for arranging them into the visual grid — it does not fetch data of its own, and any data genuinely needed by the layout chrome, such as a page title or a global filter bar, should be treated as its own concern rather than folded into whichever slot happens to load fastest. OUTPUT FORMAT The folder structure as a tree, the layout.tsx showing exactly how the slots are composed as props, and one paragraph walking through what happens on a hard refresh versus a soft in-app navigation when only one slot's URL segment is present in the current path.
Customize
Optional — swap in your own details for the highlighted parts above.
Why this works
Parallel routes solve a specific problem generic React tab-state can't: each @slot is its own independent route segment with its own data fetching, its own loading.tsx, and its own error.tsx, which means a slow panel streams in on its own schedule and a failed panel shows its own scoped error boundary, none of which requires the layout component to hand-coordinate loading or error state the way a single fetch-everything-then-render-conditionally component would have to. The default.tsx requirement is the part most first attempts miss, and the part that actually breaks in production rather than in development: without it, refreshing the browser on a URL that only specifies one slot's segment causes Next.js to 404 the entire layout, because the other slots have no matching route for that navigation and no fallback content to render instead — a bug that's invisible during normal client-side navigation, where the previous slot content is simply preserved, and only surfaces the moment someone hits refresh or shares a deep link, which is exactly the scenario a dashboard needs to handle correctly since links to specific panels get shared constantly. Separating the hard-navigation behavior (fall back to default.tsx or 404 without one) from the soft-navigation behavior (preserve whatever the unmatched slot was already showing) matters because these are genuinely two different code paths with two different failure modes, and a build that only tests one — usually soft navigation, since that's what clicking around in development actually exercises — will ship with the other silently broken until a refresh or a shared link surfaces it in front of a real user. Keeping data fetching out of layout.tsx entirely, and pushing it down into each slot's own page.tsx, is what actually delivers the independence the pattern promises — a layout that fetches shared data itself reintroduces exactly the single point of failure parallel routes exist to eliminate, since a slow or failed fetch in the layout now blocks or breaks every slot underneath it regardless of how well each individual slot is structured.
What you get back
app/dashboard/layout.tsx (with @revenue, @tickets, @utilization slots) receives { children, revenue, tickets, utilization } as props and arranges them in a CSS grid — it makes no fetch calls of its own. Refreshing on /dashboard/tickets/open renders @tickets' matching segment with the open-ticket filter applied, while @revenue and @utilization fall back to their own default.tsx (rendering their default view) instead of 404ing the whole page. Clicking between panels client-side, by contrast, preserves whichever content each slot last showed rather than resetting to default.tsx at all — the two behaviors are genuinely different and both need to render something sensible.
Verified against
Claude Code Sonnet 4.6 · 2026-07-26
v0 by Vercel 2026.7 · 2026-08-04
Changelog
- 2026-07-26 — Initial publish, verified against Claude Code (Sonnet 4.6) and v0 by Vercel on a Next.js 16 App Router dashboard with three parallel slots.
Need this built into your business?
If a prompt isn't enough — custom software, built and maintained for you — that's Scult's day job.
EXPLORE CUSTOM SOFTWARE
