Next.js
Next.js rules and best practices for Claude Code.
12.9k
- Next.js
- React
- TypeScript
- Tailwind
- Shadcn UI
- Supabase
- Zod
- Zustand
- Node.js
5 TEMPLATES
Next.js App Router + TypeScript Rules
# CLAUDE.md — Next.js App Router + TypeScript ## Routing & rendering - App Router only. Do not create files under `pages/`. - Server Components by default. Add `'use client'` only when you need state, refs, browser APIs, or event handlers. - Co-locate route segments under `app/`. Route handlers go in `route.ts`, pages in `page.tsx`, layouts in `layout.tsx`. - Use `loading.tsx`, `error.tsx`, and `not-found.tsx` for built-in boundaries — don't roll your own when the file convention exists. - Default to streaming with `<Suspense>` instead of blocking the entire route on slow data. ## Data fetching - Fetch in Server Components with `async/await`. Pass data down as props; don't refetch the same data on the client. - Use Server Actions for mutations. Return typed results — never throw raw errors back to the client. - Cache deliberately: choose between `force-cache`, `no-store`, or `revalidate: N` per `fetch` call. Don't leave it implicit. - Tag cache reads (`next: { tags: ['user-list'] }`) and invalidate them with `revalidateTag` after mutations. ## TypeScript - `strict: true` in `tsconfig.json`. No `any`, no `as` casts unless they're narrowing a `unknown`. - Type the `params` and `searchParams` of every page and layout — these are `Promise`s in current Next. - Validate every untrusted input (form data, search params, route handler bodies) with Zod before using it. ## Styling & UI - Tailwind for layout and spacing. No CSS-in-JS for static styles. - Keep one global stylesheet (`app/globals.css`) — no per-component CSS files. - Images: always use `next/image` with explicit width/height or `fill` + a sized parent. ## SEO - Export `metadata` (or `generateMetadata`) from every page. Set `title`, `description`, and `alternates.canonical`. - Generate sitemaps in `app/sitemap.ts` from your data sources, not by hand. - Use `app/[route]/opengraph-image.tsx` to ship per-page OG images, not a single shared one. ## Don't - Don't use `getServerSideProps`, `getStaticProps`, or `getInitialProps` — they don't exist in App Router. - Don't import server-only modules (`fs`, `db client`, etc.) into client components. - Don't disable TypeScript or ESLint errors to ship. - Don't use `useEffect` to fetch data you could fetch on the server.Next.js + Server Actions + Shadcn UI
# CLAUDE.md — Next.js + Server Actions + Shadcn UI ## Forms & mutations - Use Server Actions for every form. The client form component imports the action and passes it to `<form action={...}>`. - Validate input inside the action with Zod. Reject invalid input before touching the database. - Return a discriminated `{ ok: true, data } | { ok: false, error }` from every action. The client renders state from this — never throws. - Use the `useActionState` (or `useFormState`) hook to keep server-returned errors next to the form fields. - For optimistic UI, use `useOptimistic` — don't manually duplicate state. ## Shadcn UI - Components live in `components/ui/`. Generated with `npx shadcn add <component>` — keep them under your control. - Compose from primitives: never reach for a "kitchen sink" library on top of Shadcn. - For variants, extend the existing `cva` class string — don't fork the file. - Re-export Shadcn primitives from a single barrel only if you also wrap them. Otherwise import directly from `components/ui/<file>`. - Shadcn components are client components by default. If a primitive is purely presentational, copy it into a server component file and drop the `'use client'` directive. ## Validation - One Zod schema per form, exported from `lib/validators/<feature>.ts`. - Infer types: `type FormInput = z.infer<typeof formSchema>`. Never declare the type by hand. - Reuse the schema on the client (resolver for `react-hook-form`) and the server (action input check). Don't duplicate. ## Patterns - Loading states: use the `pending` flag from `useActionState` — don't track it with `useState`. - After a successful mutation, call `revalidatePath` or `revalidateTag` from the action — not `router.refresh()` from the client. - Handle redirects with `redirect()` *inside* the action. The client never decides where to go after a mutation. ## Don't - Don't wrap a Server Action in a fetch from a client. Pass it to `<form action>` or call it directly via `useTransition`. - Don't return raw `Error` objects — they don't serialize. Return shape-checked plain objects. - Don't put business logic in form components. Keep the form thin; logic lives in the action. - Don't mix Shadcn with another UI kit. Pick one design system and commit.Next.js + Supabase Auth
# CLAUDE.md — Next.js + Supabase Auth ## Setup - Use `@supabase/ssr` for the App Router. Do not use the legacy `@supabase/auth-helpers-nextjs`. - Create three Supabase clients, each in its own file: - `lib/supabase/server.ts` — for Server Components, route handlers, and Server Actions - `lib/supabase/client.ts` — for Client Components - `lib/supabase/middleware.ts` — for the middleware that refreshes session cookies ## Sessions - Run the middleware on every request. It calls `supabase.auth.getUser()` and refreshes the auth cookies. - In Server Components, always call `supabase.auth.getUser()` — never `getSession()`. `getSession` reads cookies without revalidating, so it's spoofable. - Trust `getUser()` only on the server. On the client, treat the user object as a hint and verify with the server before any sensitive UI. ## RLS (Row Level Security) - Enable RLS on every table. No exceptions. - Write `policy` blocks for each access pattern: `select`, `insert`, `update`, `delete`. Don't combine them with `using true`. - Reference `auth.uid()` in policies. Indexes on `user_id` columns are not optional. - Test policies with the SQL editor's "Run as user" feature before merging. ## Server Actions / Route Handlers - Auth checks happen at the top of every action. Pattern: ```ts const { data: { user } } = await supabase.auth.getUser() if (!user) return { ok: false, error: 'unauthorized' } ``` - Don't pass user IDs from the client. Read them from the authenticated session inside the action. ## Cookies & redirects - Sign-in / sign-out actions must call `redirect()` after the auth call so cookies flush. - Use `revalidatePath('/', 'layout')` after sign-in/out to drop cached user-dependent renders. ## Don't - Don't expose the service-role key to the browser or in any code path that runs in a Client Component. - Don't disable RLS to "fix" a query — write a policy. - Don't store auth tokens in `localStorage`. The Supabase SSR client uses cookies. - Don't use `next/router` for auth redirects in App Router code — use `redirect()` from `next/navigation`.Next.js + Tailwind + Prisma Stack
# CLAUDE.md — Next.js + Tailwind + Prisma ## Project layout - `app/` — routes - `components/` — shared components (server-first) - `lib/db.ts` — singleton Prisma client (see below) - `lib/actions/<feature>.ts` — Server Actions - `prisma/schema.prisma` — single source of truth for the data model ## Prisma client - Export a singleton from `lib/db.ts` to avoid exhausting connection pools in dev: ```ts import { PrismaClient } from '@prisma/client' const globalForPrisma = globalThis as unknown as { prisma?: PrismaClient } export const db = globalForPrisma.prisma ?? new PrismaClient() if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = db ``` - Never instantiate `new PrismaClient()` outside this file. - Run `prisma generate` on every schema change. Add it to `postinstall`. ## Data access - Query Prisma in Server Components and Server Actions only. Don't pass the Prisma client to the client. - Use `select` to project only the fields you need. Don't return entire rows by default. - Wrap multi-step writes in `prisma.$transaction`. If a write fails halfway, the rollback must be automatic. - For pagination, use cursor-based pagination (`cursor`, `take`) — not `skip` + `take`. ## Migrations - `prisma migrate dev --name <change>` for local schema changes; `prisma migrate deploy` in CI. - Never edit a migration file after merging. Generate a new migration to fix forward. - Seed data lives in `prisma/seed.ts`, runnable via `prisma db seed`. ## Tailwind - Use semantic spacing scale (`p-4`, `gap-3`) — no magic pixel values via `[12px]` unless absolutely required. - Component variants: use `cva` from `class-variance-authority`, not nested ternaries inside `className`. - Mobile-first: write base styles, then `sm:`, `md:`, `lg:`. Don't write `lg:` styles before base. ## Environment - `DATABASE_URL` and `DIRECT_URL` (for migrations) live in `.env`. Never commit `.env`. - For pooled connections (Vercel, etc.), set `DATABASE_URL` to the pooled URL and `DIRECT_URL` to the direct one in `prisma/schema.prisma`. ## Don't - Don't query Prisma from a Client Component or `useEffect`. Move it to the server. - Don't use `findFirst` when `findUnique` would do — uniqueness is a hint to the planner. - Don't disable type generation to ship. - Don't leak full DB error messages to the client.Next.js SEO + Metadata Rules
# CLAUDE.md — Next.js SEO + Metadata ## Metadata API - Every page exports `metadata` (static) or `generateMetadata` (dynamic). No exceptions. - Always set: `title`, `description`, `alternates.canonical`, `openGraph`, `twitter`, `robots`. - Title pattern: `${PageName} | ${SiteName}`. Set the suffix once via `metadata.title.template` in the root layout. - Description: 140–160 characters. Avoid keyword stuffing — write a real sentence. ## Canonical URLs - Set `alternates.canonical` to the absolute URL on every indexable page. - For paginated routes, the canonical should point to the *current* page, not page 1. Don't auto-rewrite to page 1. - Trailing slashes: pick one and stick with it via `next.config.ts` `trailingSlash`. ## Sitemap & robots - `app/sitemap.ts` exports a function returning `MetadataRoute.Sitemap`. Generate from your data — never hand-maintain. - `app/robots.ts` exports a function returning `MetadataRoute.Robots`. Reference the sitemap. - Exclude staging, admin, and search-result pages with `disallow`. Don't rely on auth alone. ## OG images - One `opengraph-image.tsx` per route segment. Use `next/og`'s `ImageResponse`. - Size: 1200×630. `runtime: 'nodejs'` if you read from the filesystem; otherwise `'edge'` is fine. - Pre-render dynamic OG images via `generateStaticParams` so they're cached at the CDN. ## Structured data (JSON-LD) - Add a `<script type="application/ld+json">` per page where it applies — `Article`, `Product`, `BreadcrumbList`, `FAQPage`, `SoftwareApplication`. - Use `dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}` — don't render JSON-LD as a child string. - Validate with Google's Rich Results Test before claiming the page is "done". ## Performance signals - Ship `priority` only on the LCP image (one per page). - `next/font` for fonts — never `<link>` to Google Fonts. - Avoid client components on static content. Server Components ship zero JS. ## Internal linking - Use `<Link>` for same-origin navigation. Never `<a href>`. - Crawlers follow `<Link>`. They do *not* follow JavaScript-only navigation. ## Don't - Don't dynamically generate canonical URLs on the client. - Don't `noindex` pages by accident — review your `robots` field per page. - Don't ship a single shared OG image when you can ship per-page images. - Don't write meta tags by hand with `<meta>` in your JSX. Use the Metadata API.
Have a CLAUDE.md template that works for you?
Send it in — we’ll credit you and publish it under the right tags.