Prompt to Create an Admin Dashboard in Next.js
AI agent prompt to build a protected Next.js admin dashboard with data tables, server-side auth guards, and a sidebar layout using Tailwind.
CursorClaude CodeCodexWindsurf Next.jsPostgreSQLTypeScriptTailwind
Give this prompt to your agent to scaffold a role-gated admin area with a persistent sidebar, sortable data tables, and server-side auth checks — without it scaffolding a full new Next.js project or installing a bloated admin framework.
Main Prompt
You are working in an existing Next.js App Router project with TypeScript, Tailwind CSS v4,and PostgreSQL. Auth is already set up (assume a `getSession()` helper exists in `src/lib/get-session.ts`that returns `{ user: { id, email, role } } | null`).
Task: add an admin dashboard at the `/admin` route group.
Requirements:- Create a `src/app/(admin)/layout.tsx` that: - Calls `getSession()` and redirects to `/login` if the session is null or `role !== 'admin'`. - Renders a persistent sidebar with links: Dashboard, Users, Settings. - Uses Tailwind for layout — a fixed-width sidebar and a scrollable main area.- Create `src/app/(admin)/dashboard/page.tsx` with: - Four KPI cards: Total Users, Active Today, Revenue (MRR), Churn Rate — all fetched from PostgreSQL using parameterized queries via `postgres` npm package. - A `<UsersTable>` server component that renders the 50 most recent users (id, email, role, created_at) with sortable column headers (URL-based sort param).- Create `src/components/admin/KpiCard.tsx` and `src/components/admin/UsersTable.tsx`.- All DB queries must use parameterized placeholders — never string interpolation.- Do not install Prisma, Drizzle, or any ORM. Use the `postgres` package directly.- Do not use any pre-built admin UI library (Refine, AdminJS, etc.).
Stop and list all planned file changes before writing any code.Implementation Notes
- The
(admin)route group parentheses keep/admin/dashboardout of the URL while sharing the layout — the agent sometimes forgets the parentheses and creates a literal/adminsegment. - Sortable columns should use
?sort=email&dir=ascURL params read in the Server Component viasearchParamsprop — no client state needed. - KPI queries should be run in parallel with
Promise.allto minimize DB round-trips.
Expected File Changes
src/app/(admin)/layout.tsx (new)src/app/(admin)/dashboard/page.tsx (new)src/components/admin/KpiCard.tsx (new)src/components/admin/UsersTable.tsx (new)src/lib/queries/admin-stats.ts (new)Acceptance Criteria
- Visiting
/dashboardas a non-admin user redirects to/login. - KPI cards show real values from PostgreSQL.
- Users table is sorted by the
sort+dirURL params server-side. bun run buildexits 0 with no type errors.
Test Commands
bun run typecheckbun run buildbun run dev# log in as admin and visit /dashboard# visit /dashboard without auth — confirm redirect to /login# add ?sort=email&dir=asc to the URL and confirm table orderCommon AI Mistakes
- Creating
src/app/admin/layout.tsxinstead ofsrc/app/(admin)/layout.tsx. - Performing the auth check client-side, which is bypassable.
- Using string interpolation in SQL queries:
`SELECT * FROM users WHERE role = '${role}'`. - Fetching KPI stats sequentially instead of using
Promise.all.
Fix Prompt
The admin layout does not redirect unauthenticated users, or the route group folder name is wrong.Fix in order:1. Rename `src/app/admin/` to `src/app/(admin)/` — the parentheses are required for a route group.2. In `layout.tsx`, add the auth check at the very top using `getSession()` and `redirect('/login')`.3. Audit all SQL query strings and replace any interpolated variables with `$1`, `$2` placeholders.Show the corrected diff only.