A Next.js 13 App Router application with Clerk in front of a full analytics dashboard — stat cards, a revenue chart, a recent-activity list, a team switcher, a command-palette search and a date-range picker, all behind a login.
The interesting part is that essentially none of the auth is written by hand. Route protection is a middleware matcher, not a wrapper around each page, and it is declared deny-by-default: everything the matcher covers requires a session unless it is explicitly named in publicRoutes. The inverse arrangement — listing what to protect — is how a new page ships unauthenticated because somebody forgot to add it to a list.
Because the check happens in middleware, an unauthenticated request never reaches a page at all. There is no session loading state, no client-side redirect, and no flash of the wrong UI while the user resolves. The matcher also deliberately skips _next and anything with a file extension, since running auth on every chunk and image costs real latency for nothing.
Route groups keep the two shells apart without appearing in the URL: (auth) renders login and register chrome-free, (dashboard) carries the nav and user menu.
Built on Clerk, PlanetScale's serverless driver, shadcn/ui over Radix, Recharts, and react-hook-form with Zod resolvers.
A Next.js 13 App Router application with Clerk handling authentication in front of a full analytics dashboard: route groups, protected pages, hosted sign-in and sign-up, and a user profile screen.
Features#
- Middleware-level route protection. Everything is private by default, and only the public routes are declared.
- Hosted sign-in and sign-up mounted as Clerk components instead of
hand-built forms, themed through
@clerk/themes. - Dashboard with stat cards, a revenue bar chart, a recent-activity list, tabs, a team switcher and a command-palette search.
- Date range picker for scoping the dashboard view.
- Profile page under the authenticated layout.
- Light and dark themes via
next-themes, no flash on first paint.
Stack#
| Framework | Next.js 13.4.7 (App Router), React 18.2 |
|---|---|
| Auth | Clerk (@clerk/nextjs, @clerk/themes) |
| Database | PlanetScale serverless driver |
| UI | shadcn/ui on Radix primitives + Tailwind CSS |
| Charts | Recharts |
| Forms | react-hook-form + Zod via @hookform/resolvers |
| Misc | cmdk (command menu), sonner (toasts), react-day-picker, date-fns |
| Env safety | @t3-oss/env-nextjs |
Route structure#
Route groups keep the two layouts apart without leaking into the URL:
app/
├── (auth)/
│ ├── layout.tsx # centred, chrome-free
│ └── (routes)/
│ ├── login/
│ └── register/
├── (dashboard)/
│ ├── layout.tsx # nav, user menu, theme toggle
│ ├── page.tsx # the dashboard
│ └── profile/
└── layout.tsx # ClerkProvider + theme providerHow the auth wiring works#
All route protection lives in the middleware:
import { authMiddleware } from '@clerk/nextjs'
export default authMiddleware({
publicRoutes: ['/api/:path*'],
})
export const config = {
matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)'],
}Two details:
- Deny by default. Every route the matcher covers requires a session unless
it is named in
publicRoutes. Listing the protected routes instead lets a new page ship unauthenticated if someone forgets to add it. - The matcher deliberately skips static assets and
_next. Running auth middleware on every image and chunk is a measurable cost for no benefit.
Because the check happens in middleware, the dashboard pages never render a loading state for the session, and there is no client-side redirect flicker. An unauthenticated request never reaches the page.
This is the 2023 API. authMiddleware was replaced by clerkMiddleware() in @clerk/nextjs v5, Next.js 16 renamed middleware.ts to proxy.ts, and Clerk now recommends await auth.protect() in each page instead of path checks in middleware. The port is written up in Clerk auth in Next.js 16: proxy.ts and auth.protect().
Running locally#
git clone https://github.com/Vette1123/next13-auth.git
cd next13-auth
yarn installCreate .env.local:
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/login
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/register
DATABASE_URL=mysql://... # PlanetScale connection stringyarn devKeys come from the Clerk dashboard. The app will not boot without them:
@t3-oss/env-nextjs validates the environment at startup, so a missing key does
not surface as a runtime error later.
Notes#
The dashboard UI started from the shadcn/ui example and was kept as the protected surface. The project is about the auth and routing wiring around it. The chart data is static.
Built by Mohamed Gado · 2023