Claude Code और Next.js full-stack: App Router की व्यावहारिक गाइड
Claude Code और Next.js App Router से full-stack फीचर बनाते समय server/client सीमा, Server Actions, API, validation, auth और review सीखें।
Claude Code से Next.js full-stack फीचर बहुत तेजी से बन सकता है। लेकिन यह गति तभी उपयोगी है जब App Router की सीमाएं साफ हों। अगर prompt अस्पष्ट है, तो server-only logic browser component में आ सकता है, हर page Client Component बन सकता है, या बिना validation वाला JSON सीधे data layer में सेव हो सकता है।
इस लेख में हम authenticated task dashboard का छोटा उदाहरण लेते हैं। इसमें App Router structure, Server Components और Client Components का अंतर, Route Handlers, Server Actions, validation, environment variables, auth boundary और Claude Code से architecture review करवाने का तरीका शामिल है।
आधिकारिक संदर्भ के लिए Next.js App Router docs, Server and Client Components, Route Handlers, Mutating Data और Backend for Frontend guide देखें। Claude Code workflow के लिए Claude Code common workflows उपयोगी है।
पहले सीमा तय करें
App Router में कठिनाई feature name याद करने में नहीं, बल्कि यह जानने में है कि code कहां चलेगा। Server Component server पर render होता है। Client Component browser में चलता है और state, event, browser API के लिए जरूरी होता है। Server Action server-side mutation function है, जिसे अक्सर form से call किया जाता है। Route Handler HTTP endpoint है, जो JSON API, webhook या external client के लिए सही है। BFF का अर्थ Backend for Frontend है, यानी किसी UI के लिए पतली backend layer।
| क्षेत्र | कब उपयोग करें | क्या रख सकते हैं | Claude Code को निर्देश |
|---|---|---|---|
| Server Component | initial render, DB read, SEO page | DB access, auth check, private API | interaction न हो तो server-first रखें |
| Client Component | form, modal, tab, optimistic UI | useState, useActionState, event handler | secrets, DB client, server-only module न लाएं |
| Server Action | UI से create, update, delete | validation, auth, mutation, revalidation | public API की तरह उपयोग न करें |
| Route Handler | external API, webhook, mobile client | JSON response, status code, signature check | input validation और auth अनिवार्य |
यह table implementation से पहले Claude Code को दें। सबसे अहम नियम है: Client Component में secrets expose न करें। केवल NEXT_PUBLIC_ prefix वाली environment variables browser के लिए होती हैं।
flowchart TD
Browser[Browser form] --> Client[Client Component]
Client --> Action[Server Action]
External[External service] --> Route[Route Handler]
Page[Server Component] --> Auth[Auth boundary]
Action --> Auth
Route --> Auth
Auth --> Data[DB ya server-only logic]
Data --> Page
Project structure
Claude Code को पहले file layout बताएं। App Router में file system ही routing model है, इसलिए अस्पष्ट prompt logic को app, components और lib में बिखेर देता है।
src/
app/
dashboard/
tasks/
page.tsx
new/
page.tsx
actions.ts
api/
tasks/
route.ts
components/
task-create-form.tsx
lib/
auth.ts
env.ts
tasks.ts
page.tsx initial UI render करता है, task-create-form.tsx browser interaction संभालता है, actions.ts UI mutation करता है, route.ts HTTP API देता है, और lib server-only application logic रखता है।
Server-only logic अलग रखें
नीचे demo के लिए in-memory storage है ताकि code copy-paste किया जा सके। production में इसे Prisma, Drizzle, Supabase या अपनी data layer से बदलें। मुख्य बात server-only है, जिससे यह module गलती से Client Component में import नहीं होगा।
// src/lib/tasks.ts
import "server-only";
export type TaskPriority = "low" | "normal" | "high";
export type Task = {
id: string;
ownerId: string;
title: string;
priority: TaskPriority;
dueDate: string | null;
createdAt: string;
};
const tasks: Task[] = [];
export async function listTasks(options: {
ownerId: string;
priority?: TaskPriority;
}) {
return tasks
.filter((task) => task.ownerId === options.ownerId)
.filter((task) => !options.priority || task.priority === options.priority)
.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
}
export async function createTask(input: {
ownerId: string;
title: string;
priority: TaskPriority;
dueDate?: string | null;
}) {
const task: Task = {
id: crypto.randomUUID(),
ownerId: input.ownerId,
title: input.title,
priority: input.priority,
dueDate: input.dueDate ?? null,
createdAt: new Date().toISOString(),
};
tasks.push(task);
return task;
}
Auth boundary भी server पर रखें। यह demo demo_user_id cookie को logged-in user मानता है। वास्तविक product में Auth.js, Clerk या internal identity provider लगाएं।
// src/lib/auth.ts
import "server-only";
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
export type CurrentUser = {
id: string;
name: string;
role: "member" | "admin";
};
export async function getCurrentUser(): Promise<CurrentUser | null> {
const cookieStore = await cookies();
const userId = cookieStore.get("demo_user_id")?.value;
if (!userId) {
return null;
}
return {
id: userId,
name: "Demo User",
role: "member",
};
}
export async function requireUser() {
const user = await getCurrentUser();
if (!user) {
redirect("/login");
}
return user;
}
export async function requireApiUser() {
return getCurrentUser();
}
Environment variables को एक जगह validate करें। इस file को Client Component में import न करें।
// src/lib/env.ts
import "server-only";
import { z } from "zod";
const EnvSchema = z.object({
DATABASE_URL: z.string().url(),
APP_SECRET: z.string().min(32),
});
export const env = EnvSchema.parse(process.env);
Server Component page
Task list Server Component में रखें। Authenticated data server पर पढ़ा जाता है, browser bundle छोटा रहता है और unnecessary client loading से बचते हैं।
// src/app/dashboard/tasks/page.tsx
import Link from "next/link";
import { requireUser } from "@/lib/auth";
import { listTasks } from "@/lib/tasks";
export default async function TasksPage() {
const user = await requireUser();
const tasks = await listTasks({ ownerId: user.id });
return (
<main className="mx-auto max-w-3xl space-y-6 p-6">
<div className="flex items-center justify-between gap-4">
<div>
<h1 className="text-2xl font-bold">Tasks</h1>
<p className="text-sm text-gray-600">Work owned by {user.name}</p>
</div>
<Link className="rounded bg-black px-4 py-2 text-white" href="/dashboard/tasks/new">
New task
</Link>
</div>
<ul className="divide-y rounded border">
{tasks.map((task) => (
<li className="flex items-center justify-between p-4" key={task.id}>
<div>
<p className="font-medium">{task.title}</p>
<p className="text-sm text-gray-500">
Priority: {task.priority} / Due: {task.dueDate ?? "none"}
</p>
</div>
</li>
))}
</ul>
</main>
);
}
Claude Code को साफ लिखें कि असली browser interaction न हो तो इस file में use client न जोड़े।
Server Action mutation
Form mutations के लिए Server Actions अच्छे हैं। Auth, validation, persistence और revalidation को एक ही function में रखें। Zod input को data layer से पहले validate करता है।
// src/app/dashboard/tasks/new/actions.ts
"use server";
import { revalidatePath } from "next/cache";
import { z } from "zod";
import { requireUser } from "@/lib/auth";
import { createTask } from "@/lib/tasks";
const CreateTaskSchema = z.object({
title: z.string().trim().min(1, "Title is required").max(80),
priority: z.enum(["low", "normal", "high"]),
dueDate: z
.string()
.trim()
.optional()
.transform((value) => (value ? value : null)),
});
export type TaskFormState = {
ok: boolean;
message?: string;
fieldErrors?: Record<string, string[]>;
};
export async function createTaskAction(
previousState: TaskFormState,
formData: FormData
): Promise<TaskFormState> {
const user = await requireUser();
const parsed = CreateTaskSchema.safeParse({
title: formData.get("title"),
priority: formData.get("priority"),
dueDate: formData.get("dueDate"),
});
if (!parsed.success) {
return {
ok: false,
fieldErrors: parsed.error.flatten().fieldErrors,
message: "Please check the form fields.",
};
}
await createTask({
ownerId: user.id,
...parsed.data,
});
revalidatePath("/dashboard/tasks");
return {
ok: true,
message: "Task created.",
};
}
Client Component केवल browser state और form rendering संभालता है। इसमें DB, auth secret या env module import न करें।
// src/components/task-create-form.tsx
"use client";
import { useActionState } from "react";
import { useFormStatus } from "react-dom";
import {
createTaskAction,
type TaskFormState,
} from "@/app/dashboard/tasks/new/actions";
const initialState: TaskFormState = {
ok: false,
};
function SubmitButton() {
const { pending } = useFormStatus();
return (
<button
className="rounded bg-black px-4 py-2 text-white disabled:opacity-50"
disabled={pending}
type="submit"
>
{pending ? "Creating..." : "Create"}
</button>
);
}
export function TaskCreateForm() {
const [state, formAction] = useActionState(createTaskAction, initialState);
return (
<form action={formAction} className="space-y-4 rounded border p-4">
<div>
<label className="block text-sm font-medium" htmlFor="title">
Title
</label>
<input
className="mt-1 w-full rounded border px-3 py-2"
id="title"
name="title"
type="text"
/>
{state.fieldErrors?.title?.map((error) => (
<p className="mt-1 text-sm text-red-600" key={error}>
{error}
</p>
))}
</div>
<div>
<label className="block text-sm font-medium" htmlFor="priority">
Priority
</label>
<select className="mt-1 w-full rounded border px-3 py-2" id="priority" name="priority">
<option value="normal">Normal</option>
<option value="high">High</option>
<option value="low">Low</option>
</select>
</div>
<div>
<label className="block text-sm font-medium" htmlFor="dueDate">
Due date
</label>
<input className="mt-1 w-full rounded border px-3 py-2" id="dueDate" name="dueDate" type="date" />
</div>
{state.message ? <p className="text-sm text-gray-700">{state.message}</p> : null}
<SubmitButton />
</form>
);
}
// src/app/dashboard/tasks/new/page.tsx
import { TaskCreateForm } from "@/components/task-create-form";
export default function NewTaskPage() {
return (
<main className="mx-auto max-w-xl p-6">
<h1 className="mb-4 text-2xl font-bold">Create a task</h1>
<TaskCreateForm />
</main>
);
}
Route Handler API
External integration, webhook, mobile client या JSON API के लिए Route Handler उपयोग करें। Server Actions UI mutation के लिए अच्छे हैं, लेकिन साफ HTTP contract का विकल्प नहीं हैं।
// src/app/api/tasks/route.ts
import { NextRequest, NextResponse } from "next/server";
import { z } from "zod";
import { requireApiUser } from "@/lib/auth";
import { createTask, listTasks } from "@/lib/tasks";
export const runtime = "nodejs";
const PrioritySchema = z.enum(["low", "normal", "high"]);
const CreateTaskApiSchema = z.object({
title: z.string().trim().min(1).max(80),
priority: PrioritySchema.default("normal"),
dueDate: z.string().date().nullable().optional(),
});
export async function GET(request: NextRequest) {
const user = await requireApiUser();
if (!user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const priority = request.nextUrl.searchParams.get("priority");
const parsedPriority = priority ? PrioritySchema.safeParse(priority) : null;
if (parsedPriority && !parsedPriority.success) {
return NextResponse.json({ error: "Invalid priority" }, { status: 400 });
}
const tasks = await listTasks({
ownerId: user.id,
priority: parsedPriority?.data,
});
return NextResponse.json({ data: tasks });
}
export async function POST(request: NextRequest) {
const user = await requireApiUser();
if (!user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const body = await request.json().catch(() => null);
const parsed = CreateTaskApiSchema.safeParse(body);
if (!parsed.success) {
return NextResponse.json(
{ error: "Invalid request body", details: parsed.error.flatten().fieldErrors },
{ status: 400 }
);
}
const task = await createTask({
ownerId: user.id,
...parsed.data,
});
return NextResponse.json({ data: task }, { status: 201 });
}
व्यावहारिक use cases
पहला use case internal request dashboard है। list Server Component में रहती है, request creation Server Action से होता है, और Slack या दूसरे tool की notification Route Handler से भेजी जाती है।
दूसरा use case SaaS settings page है। billing settings, team invitations और API key creation में UI हल्का रखना चाहिए, लेकिन permission check Server Action में होना चाहिए।
तीसरा use case CMS या product admin है। initial list server पर render होती है, जबकि image upload और webhook Route Handler में रहते हैं। Database changes के लिए DB migration automation guide देखें।
चौथा use case BFF architecture है। Browser से कई third-party APIs सीधे call करने के बजाय उन्हें Next.js Route Handler के पीछे रखें। इससे secrets server पर रहते हैं और UI को stable contract मिलता है।
आम गलतियां
सबसे बड़ा जोखिम boundary blur होना है। जांचें कि Client Component DB helper import तो नहीं कर रहा, initial data loading बेवजह useEffect में तो नहीं गया, Server Action public API की तरह तो नहीं है, और Route Handler request.json() को बिना validation store तो नहीं कर रहा।
Authentication केवल UI छिपाने से नहीं होता। Button hide करना authorization नहीं है। Server Actions और Route Handlers दोनों में user, ownership और role check करें। अधिक जानकारी के लिए authentication implementation guide देखें।
“ठीक से fix करो” जैसे vague prompts से बचें। App Router का छोटा change भी cache, revalidation, form state और auth boundary को प्रभावित कर सकता है। हर बार changed files, forbidden changes और verification commands दें।
Claude Code review prompt
Implementation के बाद पहले review मांगें, edit नहीं।
You are reviewing a Next.js App Router full-stack change.
Scope:
- src/app/dashboard/tasks
- src/app/api/tasks/route.ts
- src/components/task-create-form.tsx
- src/lib/auth.ts
- src/lib/tasks.ts
- src/lib/env.ts
Check:
1. No secrets, DB clients, or server-only modules are imported by Client Components.
2. Server Components are not converted to Client Components without a real interaction need.
3. Server Actions validate input, check auth, mutate data, and revalidate the affected path.
4. Route Handlers return correct HTTP status codes and validate JSON bodies.
5. Auth is enforced on the server, not only hidden in the UI.
6. Tests or manual verification steps are listed for each risk.
Do not edit files yet. Return findings by severity with file paths and concrete fixes.
उसके बाद एक-एक finding fix करें। कम से कम lint, typecheck, related tests, manual form submission और unauthenticated API request का 401 response verify करें। Test planning के लिए testing strategy guide देखें।
ClaudeCodeLab training और templates
Solo project में इस लेख की structure और prompt काफी हैं। Team में boundary rules, review checklist, auth rules और environment policy को reusable template बनाना बेहतर है।
ClaudeCodeLab Claude Code training and templates और team adoption consultation देता है। Next.js admin screens, CMS, SaaS settings और review loop को अपनी repository rules के अनुसार standardize करना हो तो यह उपयोगी है।
निष्कर्ष
Claude Code Next.js full-stack development को तेज कर सकता है, लेकिन पहले server और browser की boundary तय करनी होती है। Initial rendering के लिए Server Components, interaction के लिए Client Components, UI mutations के लिए Server Actions, और external APIs के लिए Route Handlers रखें।
इस लेख का workflow आजमाने पर सबसे बड़ा फायदा ज्यादा code generate होना नहीं था, बल्कि review noise कम होना था। जब boundary table और file structure पहले Claude Code को दिए गए, तो बाद के edits ज्यादातर UI copy और छोटे behavior changes तक सीमित रहे। जब पहला prompt vague था, form logic और API logic मिल गए और review implementation से ज्यादा लंबा हो गया।
मुफ़्त PDF: Claude Code cheatsheet
Email डालें और commands, review habits तथा safe workflow वाली एक-page PDF पाएँ.
हम आपका data सुरक्षित रखते हैं और spam नहीं भेजते.
लेखक के बारे में
Masa
Claude Code workflow और team adoption पर काम करने वाला engineer.
संबंधित लेख
Claude Code Obsidian to CLAUDE.md workflow: context बार-बार न समझाएं
Obsidian notes को CLAUDE.md operating notes में बदलकर Claude Code sessions को resume करना आसान बनाएं.
Claude Code Revenue CTA Routing: article से PDF, Gumroad और consultation तक
Reader intent के आधार पर free PDF, Gumroad products और consultation तक CTA route करने वाला workflow.
Claude Code टीम हैंडऑफ नियम: review proof, permissions, rollback और revenue path
Claude Code टीम काम के लिए evidence, permission rules, rollback, free PDF, Gumroad और consultation path वाला handoff.