---
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!
reset()}>Try again
);
}
```
## Loading States
```tsx
// app/projects/loading.tsx
export default function Loading() {
return ;
}
```