# AGENTS.md - AI Coding Agent Reference This file provides essential information for AI coding agents working on this project. It contains project-specific details, conventions, and guidelines that complement the README. --- ## Project Overview **Next.js Admin Dashboard Starter** is a production-ready admin dashboard template built with: - **Framework**: Next.js 16 (App Router) - **Language**: TypeScript 5.7 - **Styling**: Tailwind CSS v4 - **UI Components**: shadcn/ui (New York style) - **Authentication**: Clerk (with Organizations/Billing support) - **Error Tracking**: Sentry - **Charts**: Recharts - **Containerization**: Docker (Node.js & Bun Dockerfiles) - **Package Manager**: Bun (preferred) or npm The project follows a feature-based folder structure designed for scalability in SaaS applications, internal tools, and admin panels. --- ## Technology Stack Details ### Core Framework & Runtime - Next.js 16.0.10 with App Router - React 19.2.0 - TypeScript 5.7.2 with strict mode enabled ### Styling & UI - Tailwind CSS v4 (using `@import 'tailwindcss'` syntax) - PostCSS with `@tailwindcss/postcss` plugin - shadcn/ui component library (Radix UI primitives) - CSS custom properties for theming (OKLCH color format) ### State Management - Zustand 5.x for local UI state in the stateful demo features - Nuqs for URL search params state management - TanStack Form + Zod for form handling (`createFormHook` + shadcn `Field`-anatomy components) ### Data Fetching & Caching - TanStack React Query for data fetching, caching, and mutations - Server-side prefetching with `HydrationBoundary` + `dehydrate` - Client-side `useQuery` + nuqs `shallow: true` for tables (no RSC round-trips on pagination/filter) - `useMutation` + `invalidateQueries` for form submissions - Query client singleton in `src/lib/query-client.ts` ### Authentication & Authorization - Clerk for authentication and user management - Clerk Organizations for multi-tenant workspaces - Clerk Billing for subscription management (B2B) - Client-side RBAC for navigation visibility ### Data & APIs - TanStack Table for data tables - TanStack React Query for data fetching and mutations - Recharts for analytics/charts - Service layer per feature (`api/types.ts` → `api/service.ts` → `api/queries.ts`) - Route handlers at `src/app/api/` (for Route Handler or BFF patterns) - Mock data in `src/constants/mock-api*.ts` (default, swap via service layer) - API client utility in `src/lib/api-client.ts` (for fetch-based patterns) ### Development Tools - ESLint 8.x with Next.js core-web-vitals config - Prettier 3.x with prettier-plugin-tailwindcss - Husky for git hooks - lint-staged for pre-commit formatting --- ## Project Structure ``` /src ├── app/ # Next.js App Router │ ├── auth/ # Authentication routes (sign-in, sign-up) │ ├── dashboard/ # Dashboard routes │ │ ├── overview/ # Parallel routes (@area_stats, @bar_stats, etc.) │ │ ├── product/ # Product management pages │ │ ├── kanban/ # Kanban board page │ │ ├── chat/ # Messaging page │ │ ├── ai-chat/ # AI chat streaming demo │ │ ├── notifications/ # Notifications page │ │ ├── workspaces/ # Organization management │ │ ├── billing/ # Subscription billing │ │ ├── exclusive/ # Pro plan feature example │ │ └── profile/ # User profile │ ├── api/ # API routes (if any) │ ├── layout.tsx # Root layout with providers │ ├── page.tsx # Landing page │ ├── global-error.tsx # Global error boundary │ └── not-found.tsx # 404 page │ ├── components/ │ ├── ui/ # shadcn/ui components (50+ components) │ ├── layout/ # Layout components (sidebar, header, etc.) │ ├── forms/ # Field components (shadcn TanStack Form anatomy) + demos │ ├── themes/ # Theme system components │ ├── kbar/ # Command+K search bar │ ├── icons.tsx # Icon registry │ └── ... │ ├── features/ # Feature-based modules │ ├── auth/ # Authentication components │ ├── overview/ # Dashboard analytics │ ├── products/ # Product management (React Query + nuqs) │ │ ├── api/ │ │ │ ├── types.ts # Type contract (response shapes, filters, payloads) │ │ │ ├── service.ts # Data access layer (swap for your backend) │ │ │ └── queries.ts # React Query options + key factories │ │ ├── components/ # Listing, form, table components │ │ ├── schemas/ # Zod schemas │ │ └── constants/ # Filter options │ ├── users/ # User management (React Query + nuqs) │ │ ├── api/ # Same pattern: types.ts → service.ts → queries.ts │ │ └── components/ # Listing, table components │ ├── react-query-demo/ # React Query showcase (Pokemon API) │ ├── kanban/ # Kanban board with dnd-kit │ ├── chat/ # Messaging UI (conversations, bubbles, composer) │ ├── ai-chat/ # Scripted useChat streaming demo │ ├── notifications/ # Notification center & store │ └── profile/ # Profile management │ ├── config/ # Configuration files │ ├── nav-config.ts # Navigation with RBAC │ └── ... │ ├── hooks/ # Custom React hooks │ ├── use-nav.ts # RBAC navigation filtering │ ├── use-data-table.ts # Data table state │ └── ... │ ├── lib/ # Utility functions │ ├── utils.ts # cn() and formatters │ ├── searchparams.ts # Search param utilities │ └── ... │ ├── types/ # TypeScript type definitions │ └── index.ts # Core types (NavItem, etc.) │ └── styles/ # Global styles ├── globals.css # Tailwind imports + view transitions ├── theme.css # Theme imports └── themes/ # Individual theme files /docs # Documentation │ ├── clerk_setup.md # Clerk configuration guide │ ├── nav-rbac.md # Navigation RBAC documentation │ └── themes.md # Theme customization guide /scripts # Dev tooling ├── cleanup.js # Feature removal, run via `bun run cleanup` (templates in cleanup-templates/, typechecked) └── cleanup-templates/ # Replacement files cleanup.js copies into the repo Dockerfile # Node.js production Dockerfile Dockerfile.bun # Bun production Dockerfile .dockerignore # Docker build exclusions ``` --- ## Build & Development Commands ```bash # Install dependencies bun install # Development server bun run dev # Starts at http://localhost:3000 # Build for production bun run build # Start production server bun run start # Linting bun run lint # Run ESLint bun run lint:fix # Fix ESLint issues and format bun run lint:strict # Zero warnings tolerance bun run typecheck # tsc --noEmit # Formatting bun run format # Format with Prettier bun run format:check # Check formatting # Git hooks bun run prepare # Install Husky hooks ``` --- ## Environment Configuration Copy `env.example.txt` to `.env.local` and configure: ### Required for Authentication (Clerk) ```env NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_... CLERK_SECRET_KEY=sk_... # Redirect URLs NEXT_PUBLIC_CLERK_SIGN_IN_URL="/auth/sign-in" NEXT_PUBLIC_CLERK_SIGN_UP_URL="/auth/sign-up" NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL="/dashboard/overview" NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL="/dashboard/overview" ``` ### Optional for Error Tracking (Sentry) ```env NEXT_PUBLIC_SENTRY_DSN=https://...@....ingest.sentry.io/... NEXT_PUBLIC_SENTRY_ORG=your-org NEXT_PUBLIC_SENTRY_PROJECT=your-project SENTRY_AUTH_TOKEN=sntrys_... NEXT_PUBLIC_SENTRY_DISABLED="false" # Set to "true" to disable in dev ``` **Note**: Clerk supports "keyless mode" - the app works without API keys for initial development. --- ## Code Style Guidelines ### TypeScript - Strict mode enabled - Use explicit return types for public functions - Prefer interface over type for object definitions - Use `@/*` alias for imports from src ### Formatting (Prettier) ```json { "singleQuote": true, "jsxSingleQuote": true, "semi": true, "trailingComma": "none", "tabWidth": 2, "arrowParens": "always" } ``` ### ESLint Rules - `@typescript-eslint/no-unused-vars`: warn - `no-console`: warn - `react-hooks/exhaustive-deps`: warn - `import/no-unresolved`: off (handled by TypeScript) ### Component Conventions - Use function declarations for components: `function ComponentName() {}` - Props interface named `{ComponentName}Props` - shadcn/ui components use `cn()` utility for class merging - Server components by default, `'use client'` only when needed --- ## Theming System The project uses a sophisticated multi-theme system with 10 built-in themes: - `vercel` (default) - `claude` - `discord` - `supabase` - `mono` - `notebook` - `light-green` - `zen` - `astro-vista` - `whatsapp` ### Theme Files - CSS files: `src/styles/themes/{theme-name}.css` - Theme registry: `src/components/themes/theme.config.ts` - Font config: `src/components/themes/font.config.ts` - Active theme provider: `src/components/themes/active-theme.tsx` ### Adding a New Theme 1. Create `src/styles/themes/your-theme.css` with `[data-theme='your-theme']` selector 2. Import in `src/styles/theme.css` 3. Add to `THEMES` array in `src/components/themes/theme.config.ts` 4. (Optional) Add fonts in `font.config.ts` 5. (Optional) Set as default in `theme.config.ts` See `docs/themes.md` for detailed theming guide. --- ## Navigation & RBAC System ### Navigation Configuration Navigation is organized into groups in `src/config/nav-config.ts`: ```typescript import { NavGroup } from '@/types'; export const navGroups: NavGroup[] = [ { label: 'Overview', items: [ { title: 'Dashboard', url: '/dashboard/overview', icon: 'dashboard', shortcut: ['d', 'd'], items: [], access: { requireOrg: true } // RBAC check } ] } ]; ``` ### Access Control Properties - `requireOrg: boolean` - Requires active organization - `permission: string` - Requires specific permission - `role: string` - Requires specific role - `plan: string` - Requires specific subscription plan - `feature: string` - Requires specific feature ### Client-Side Filtering The `useFilteredNavItems()` hook in `src/hooks/use-nav.ts` filters navigation client-side using Clerk's `useOrganization()` and `useUser()` hooks. This is for UX only - actual security checks must happen server-side. --- ## Authentication Patterns ### Protected Routes Dashboard routes use Clerk's middleware pattern. Pages that require organization: ```tsx import { auth } from '@clerk/nextjs'; import { redirect } from 'next/navigation'; export default async function Page() { const { orgId } = await auth(); if (!orgId) redirect('/dashboard/workspaces'); // ... } ``` ### Plan/Feature Protection Use Clerk's `` component for client-side: ```tsx import { Protect } from '@clerk/nextjs'; }> ; ``` Use `has()` function for server-side checks: ```tsx import { auth } from '@clerk/nextjs'; const { has } = await auth(); const hasFeature = has({ feature: 'premium_access' }); ``` --- ## Data Fetching Patterns ### Service Layer Architecture Each feature has a three-file API layer: ``` src/features//api/ types.ts ← Type contract (response shapes, filters, payloads) service.ts ← Data access functions (the ONE file to swap for your backend) queries.ts ← React Query options + query key factories (stable, never changes) ``` **`service.ts` is the only file you modify when connecting to a real backend.** Queries and components import from it — they never change. #### Backend Patterns | Pattern | How to implement | | -------------------------------------------------- | ------------------------------------------------------------------------------------------- | | **Server Actions + ORM** (Prisma/Drizzle/Supabase) | Add `'use server'` at top of `service.ts`, call ORM directly | | **Route Handlers + ORM** | `service.ts` calls `/api/` routes via `apiClient`, route handlers call ORM | | **BFF** (Next.js proxies to Laravel/Go/etc.) | `service.ts` calls `/api/` routes via `apiClient`, route handlers proxy to external backend | | **Direct external API** (frontend-only) | `service.ts` calls external URL via `fetch()` | | **Mock** (default) | `service.ts` calls in-memory fake data stores | Route handlers at `src/app/api/` are ready for patterns 2 and 3. `src/lib/api-client.ts` provides a typed `fetch` wrapper. ### Query Key Factories Each feature defines a key factory in `queries.ts` for type-safe, hierarchical cache invalidation: ```tsx export const entityKeys = { all: ['entities'] as const, list: (filters: EntityFilters) => [...entityKeys.all, 'list', filters] as const, detail: (id: number) => [...entityKeys.all, 'detail', id] as const }; // Usage in queryOptions queryKey: entityKeys.list(filters); // Usage in mutations — invalidate all entity queries queryClient.invalidateQueries({ queryKey: entityKeys.all }); ``` ### React Query (Default for all new pages) The project uses TanStack React Query with server-side prefetching and client-side cache management: 1. **Query options** defined in `queries.ts` — shared between server prefetch and client hooks 2. **Server prefetch** using `void queryClient.prefetchQuery()` + `HydrationBoundary` + `dehydrate` — `void` (fire-and-forget) is the standard TanStack pattern for Next.js App Router 3. **Client fetch** using `useSuspenseQuery()` — integrates with React Suspense so prefetched data streams in without showing a loading skeleton on first load 4. **Suspense boundary** wraps the client component — shows a fallback skeleton only on subsequent client-side navigations when cache is empty ```tsx // Server component: prefetch + dehydrate const queryClient = getQueryClient(); void queryClient.prefetchQuery(entitiesQueryOptions(filters)); // void, not await return ( }> ); // Client component: useSuspenseQuery (not useQuery) const { data } = useSuspenseQuery(entitiesQueryOptions(filters)); ``` **Why `void` + `useSuspenseQuery`:** - `void` fires the prefetch without blocking the server component - `useSuspenseQuery` integrates with React Suspense — the pending query streams in via Next.js streaming SSR - With `}>`: skeleton shows immediately while data streams in — this is expected behavior, the skeleton IS the Suspense fallback during streaming - Without `` wrapper: no skeleton, but the previous page stays visible until data fully resolves (feels like a slow navigation) - Once data is cached (within `staleTime`), subsequent visits are instant — no skeleton **Why NOT `useQuery`:** - `useQuery` doesn't integrate with Suspense — returns `isLoading: true` and you must handle loading state manually - Hydrated pending queries from `void` prefetch won't prevent the loading state - Results in skeleton flash even when data is prefetched ### Mutations Components import service functions for mutations. Use query key factories for invalidation: ```tsx import { createEntity } from '../api/service'; import { entityKeys } from '../api/queries'; const mutation = useMutation({ mutationFn: (data) => createEntity(data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: entityKeys.all }); toast.success('Created'); } }); ``` ### URL State Management Use `nuqs` for search params state: - `searchParamsCache` (server) — reads params in server components - `useQueryState` (client) — reads/writes params in client components with `shallow: true` ### Data Tables Tables use TanStack Table with React Query: - Query options in `features/*/api/queries.ts` - Column definitions in `features/*/components/*-tables/columns.tsx` - Table component in `src/components/ui/table/data-table.tsx` - Column pinning via `initialState.columnPinning` in `useDataTable` --- ## Error Handling & Monitoring ### Sentry Integration Sentry is configured for both client and server: - Client config: `src/instrumentation-client.ts` - Server config: `src/instrumentation.ts` - Global error: `src/app/global-error.tsx` To disable Sentry in development: ```env NEXT_PUBLIC_SENTRY_DISABLED="true" ``` ### Error Boundaries - `global-error.tsx` - Catches all errors, reports to Sentry - Parallel route `error.tsx` files for specific sections --- ## Testing Strategy **Note**: This project does not include a test suite by default. Consider adding: - **Unit tests**: Vitest or Jest for utilities and hooks - **Component tests**: React Testing Library for UI components - **E2E tests**: Playwright for critical user flows Recommended test locations: ``` /src /__tests__ # Unit tests /features/*/tests # Feature tests /e2e # Playwright tests ``` --- ## Deployment Canonical guide: [docs/deployment.md](./docs/deployment.md) (Vercel, production environment variables, Docker). ### Vercel (Recommended) 1. Connect repository to Vercel 2. Add environment variables in dashboard 3. Deploy ### Environment Variables for Production Ensure these are set in your deployment platform: - `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` - `CLERK_SECRET_KEY` - All `NEXT_PUBLIC_*` variables for client-side access - `SENTRY_*` variables if using error tracking ### Docker Production-ready Dockerfiles are included: - `Dockerfile` — Node.js-based - `Dockerfile.bun` — Bun-based Both use `output: 'standalone'` in `next.config.ts`. Pass `NEXT_PUBLIC_*` vars as `--build-arg` at build time, and runtime secrets via `-e` at run time. ### Build Considerations - Output: `standalone` (optimized for Docker/self-hosting) - Images: Configured for `api.slingacademy.com`, `img.clerk.com`, `clerk.com` - Sentry source maps uploaded automatically in CI --- ## Feature Cleanup System A single `scripts/cleanup.js` file handles removal of optional features: ```bash # Interactive mode — prompts for each feature node scripts/cleanup.js --interactive # Remove specific features node scripts/cleanup.js clerk # Remove auth/org/billing node scripts/cleanup.js kanban # Remove kanban board node scripts/cleanup.js chat # Remove messaging UI node scripts/cleanup.js ai-chat # Remove AI chat demo node scripts/cleanup.js notifications # Remove notification center node scripts/cleanup.js themes # Keep one theme, remove rest node scripts/cleanup.js sentry # Remove error tracking # Remove multiple at once node scripts/cleanup.js kanban chat notifications # Preview without changing files node scripts/cleanup.js --dry-run kanban # List all features node scripts/cleanup.js --list ``` **Safety**: Script requires git repository with at least one commit. Use `--force` to skip. Replacement files live in `scripts/cleanup-templates/` as real `.ts`/`.tsx` files typechecked by `tsc` and `next build`, so template rot fails loudly instead of shipping broken code. After cleanup, delete `scripts/cleanup.js` and `scripts/cleanup-templates/` — the dev server message auto-cleans on next start. --- ## Icon System **All icons come from a single source: `src/components/icons.tsx`.** The project uses `@tabler/icons-react` as the sole icon package. Every icon is re-exported through a centralized `Icons` object — **never import directly from `@tabler/icons-react` or any other icon package**. ### Usage ```tsx import { Icons } from '@/components/icons'; // In JSX // Passing as a prop icon={Icons.check} ``` ### Adding a New Icon 1. Import the tabler icon in `src/components/icons.tsx` 2. Add a semantic key to the `Icons` object 3. Use `Icons.yourKey` everywhere — never the raw import ```tsx // In src/components/icons.tsx import { IconNewIcon } from '@tabler/icons-react'; export const Icons = { // ...existing icons newIcon: IconNewIcon }; ``` ### Available Icon Categories | Category | Example Keys | | --------------- | ----------------------------------------------------------------------------- | | General | `check`, `close`, `search`, `settings`, `trash`, `spinner`, `info`, `warning` | | Navigation | `chevronDown`, `chevronLeft`, `chevronRight`, `chevronUp`, `chevronsUpDown` | | Layout | `dashboard`, `kanban`, `panelLeft` | | User | `user`, `account`, `profile`, `teams` | | Communication | `chat`, `notification`, `phone`, `video`, `send` | | Files | `page`, `post`, `media`, `fileTypePdf`, `fileTypeDoc` | | Actions | `add`, `edit`, `upload`, `share`, `login`, `logout` | | Theme | `sun`, `moon`, `brightness`, `laptop`, `palette` | | Text formatting | `bold`, `italic`, `underline`, `text` | | Data / Charts | `trendingUp`, `trendingDown`, `eyeOff`, `adjustments` | ### Icon Showcase Page Browse all available icons at `/dashboard/elements/icons` — a searchable grid of every icon in the registry. ### Why This Pattern? - **Single source of truth** — swap icon packages by editing one file - **Semantic naming** — `Icons.trash` is clearer than `IconTrash` scattered across files - **Discoverability** — autocomplete on `Icons.` shows every available icon - **No direct dependencies** — components never couple to a specific icon package --- ## Common Development Tasks ### Adding a New Feature (End-to-End) 1. Create `src/features//api/types.ts` — response types, filter types, mutation payloads 2. Create `src/features//api/service.ts` — data access functions (mock by default) 3. Create `src/features//api/queries.ts` — query key factory + `queryOptions` 4. Create page route: `src/app/dashboard//page.tsx` 5. Create feature components in `src/features//components/` 6. Add navigation item in `src/config/nav-config.ts` 7. (Optional) Add route handlers in `src/app/api//` for REST API patterns 8. (Optional) Register new icon in `src/components/icons.tsx` ### Adding a New API Route 1. Create: `src/app/api/my-route/route.ts` 2. Export HTTP method handlers: `GET`, `POST`, etc. 3. For BFF pattern: proxy requests to your external backend ### Adding a shadcn Component ```bash npx shadcn add component-name ``` ### Adding a New Theme See "Theming System" section above or `docs/themes.md`. --- ## Troubleshooting ### Common Issues **Build fails with Tailwind errors** - Ensure using Tailwind CSS v4 syntax (`@import 'tailwindcss'`) - Check `postcss.config.js` uses `@tailwindcss/postcss` **Clerk keyless mode popup** - Normal in development without API keys - Click popup to claim application or set env variables **Theme not applying** - Check theme name matches in CSS `[data-theme]` and `theme.config.ts` - Verify theme CSS is imported in `theme.css` **Navigation items not showing** - Check `access` property in nav config - Verify user has required org/permission/role --- ## External Documentation - [Next.js App Router](https://nextjs.org/docs/app) - [Clerk Next.js SDK](https://clerk.com/docs/references/nextjs) - [shadcn/ui](https://ui.shadcn.com/docs) - [Tailwind CSS v4](https://tailwindcss.com/docs) - [TanStack Table](https://tanstack.com/table/latest) - [Sentry Next.js](https://docs.sentry.io/platforms/javascript/guides/nextjs/) --- ## Notes for AI Agents 1. **Always use `cn()` for className merging** - never concatenate strings manually 2. **Respect the feature-based structure** - put new feature code in `src/features/` 3. **Server components by default** - only add `'use client'` when using browser APIs or React hooks 4. **Type safety first** - avoid `any`, prefer explicit types 5. **Follow existing patterns** - look at similar components before creating new ones 6. **Environment variables** - prefix with `NEXT_PUBLIC_` for client-side access 7. **shadcn components** - don't modify files in `src/components/ui/` directly; extend them instead 8. **Icons** - NEVER import icons directly from `@tabler/icons-react` or any other icon package. All icons must be registered in `src/components/icons.tsx` and imported as `import { Icons } from '@/components/icons'`. To add a new icon: add the tabler import to `icons.tsx`, add a semantic key to the `Icons` object, then use `Icons.keyName` in your component. 9. **Page headers** - Always use `PageContainer` props (`pageTitle`, `pageDescription`, `pageHeaderAction`) for page headers. Never import `` manually in pages — `PageContainer` handles that internally. 10. **Forms** - Use `useAppForm` from `@/lib/form` with `form.AppField` rendering the shared field components (`field.TextField`, `field.SelectField`, …) from `@/components/forms/fields`. Each component follows the official shadcn TanStack Form anatomy; drop down to raw `form.Field` render props for one-off custom fields. Never use `useState` inside a render prop — extract stateful controls into components. 11. **Button loading** - Use `