Next.js dashboard scaffold
Every dashboard app has two worlds: a public marketing surface anyone can see, and a protected dashboard behind auth. Model that split with App Router route groups from day one so layouts, middleware, and styling never bleed across the boundary.
Directory layout
app/
layout.tsx # root: fonts, providers, <html>/<body>
(marketing)/
layout.tsx # public shell: top nav + footer
page.tsx # home page with sign-in CTAs
dashboard/
layout.tsx # protected shell: sidebar + header
page.tsx # dashboard overview
settings/page.tsx # one nested view to prove the pattern
components/ # shared UI (shadcn lives in components/ui)
lib/ # db client, utils
(marketing)keeps public pages at/without leaking the marketing layout into/dashboard.- The dashboard lives under a literal
/dashboardsegment — a single path prefix makes route protection one matcher in middleware instead of a list.
The home page CTA
The home page renders for everyone. Start with static links to /sign-in and
/sign-up — once Clerk is wired (clerk-nextjs-auth), swap them for
<SignedIn> / <SignedOut> branches so signed-in users see Open dashboard
instead.
// app/(marketing)/page.tsx
import Link from "next/link";
export default function Home() {
return (
<section>
<h1>Your product, one dashboard.</h1>
<Link href="/sign-in">Sign in</Link>
<Link href="/sign-up">Get started</Link>
</section>
);
}
Layout contracts
- Root layout owns
<html>, fonts, and global providers (auth provider, theme). Nothing visual. - Marketing layout owns the public chrome: slim top nav (logo + the same sign-in CTAs) and footer.
- Dashboard layout owns the app chrome: sidebar navigation, header with
the user menu. It assumes the user is authenticated — enforcement belongs
in middleware (see
clerk-nextjs-auth), not scattered page checks.
Conventions
- Server Components by default; add
"use client"only where there's real interactivity. - Co-locate one-off pieces next to their route; promote to
components/only when a second route needs them. - Data access goes through
lib/(seedrizzle-postgres-docker) — pages never import the db driver directly.
Pair with clerk-nextjs-auth for the auth wiring, shadcn-dashboard-ui for
the dashboard chrome, and drizzle-postgres-docker for persistence.