--- title: LLMs Best Practices - Next.js url: https://colingoudie.me/llms-nextjs.txt description: Next.js App Router conventions for AI-assisted development related: - https://colingoudie.me/llms.txt --- # LLMs Best Practices - Next.js Include this in your CLAUDE.md or similar AI assistant configuration file. See also: https://colingoudie.me/llms.txt ## Server Actions First Prefer Server Actions over API routes for mutations: ```tsx // app/actions/projects.ts "use server"; import { revalidatePath } from "next/cache"; export async function createProject(formData: FormData) { const name = formData.get("name") as string; if (!name) { return { error: "Name is required" }; } const project = await db.project.create({ data: { name } }); revalidatePath("/projects"); return { success: true, project }; } ``` ```tsx // components/CreateProjectForm.tsx "use client"; import { createProject } from "@/app/actions/projects"; export function CreateProjectForm() { return (
); } ``` ## Thin Pages Principle Pages should be thin orchestrators, not business logic containers: ```tsx // GOOD - page fetches data, delegates rendering // app/projects/[id]/page.tsx import { getProject } from "@/lib/queries/projects"; import { ProjectDetail } from "@/components/projects/ProjectDetail"; export default async function ProjectPage({ params }: { params: { id: string } }) { const project = await getProject(params.id); if (!project) { notFound(); } return ; } ``` ## TanStack Query Patterns For client-side data fetching and caching: ```tsx // lib/queries/use-projects.ts import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; export function useProjects() { return useQuery({ queryKey: ["projects"], queryFn: async () => { const res = await fetch("/api/projects"); if (!res.ok) throw new Error("Failed to fetch"); return res.json(); }, }); } export function useCreateProject() { const queryClient = useQueryClient(); return useMutation({ mutationFn: async (data: CreateProjectInput) => { const res = await fetch("/api/projects", { method: "POST", body: JSON.stringify(data), }); if (!res.ok) throw new Error("Failed to create"); return res.json(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["projects"] }); }, }); } ``` ### Query Keys Convention ```typescript const queryKeys = { projects: { all: ["projects"] as const, list: (filters: Filters) => ["projects", "list", filters] as const, detail: (id: string) => ["projects", "detail", id] as const, }, }; ``` ## Middleware for Cross-Cutting Concerns ```typescript // middleware.ts import { NextResponse } from "next/server"; import type { NextRequest } from "next/server"; export function middleware(request: NextRequest) { const token = request.cookies.get("auth-token"); if (!token && request.nextUrl.pathname.startsWith("/dashboard")) { return NextResponse.redirect(new URL("/login", request.url)); } const response = NextResponse.next(); response.headers.set("x-request-id", crypto.randomUUID()); return response; } export const config = { matcher: ["/dashboard/:path*", "/api/:path*"], }; ``` ## Supabase Client Patterns ### Server Components ```typescript // lib/supabase/server.ts import { createServerClient } from "@supabase/ssr"; import { cookies } from "next/headers"; export function createClient() { const cookieStore = cookies(); return createServerClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, { cookies: { get(name: string) { return cookieStore.get(name)?.value; }, }, } ); } ``` ### Client Components ```typescript // lib/supabase/client.ts import { createBrowserClient } from "@supabase/ssr"; export function createClient() { return createBrowserClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! ); } ``` ### Usage Pattern ```tsx // Server Component import { createClient } from "@/lib/supabase/server"; export default async function ProjectsPage() { const supabase = createClient(); const { data: projects } = await supabase .from("projects") .select("*") .order("created_at", { ascending: false }); return ; } ``` ## File Structure ``` app/ (auth)/ login/page.tsx layout.tsx (dashboard)/ projects/ [id]/page.tsx page.tsx layout.tsx api/ webhooks/ route.ts actions/ projects.ts layout.tsx components/ ui/ # shadcn/ui wrappers projects/ # Feature components shared/ # Shared components lib/ supabase/ queries/ utils/ ``` ## Error Handling ```tsx // app/projects/[id]/error.tsx "use client"; export default function Error({ error, reset, }: { error: Error & { digest?: string }; reset: () => void; }) { return (

Something went wrong!

); } ``` ## Loading States ```tsx // app/projects/loading.tsx export default function Loading() { return ; } ```