Next.js 15 — не просто «React с SSR». Это Server Components, Server Actions, partial prerendering и три уровня кэша. Claude Code помогает всё это собрать правильно — если знает, что App Router работает иначе, чем Pages Router.

Канал с гайдами и контентом по claude code, выкладываем новости (когда режут лимиты в 10 раз) и какие инструменты через claude реализуем для проектов, канал: https://t.me/claudedevolper

CLAUDE.md под Next.js 15

## Стек
- Next.js 15 (App Router), React 19
- TypeScript strict
- Prisma + PostgreSQL
- Tailwind + shadcn/ui
- Auth.js v5 (NextAuth)

## Правила
- Это Next.js 15 App Router. НЕ Pages Router.
- По умолчанию компонент — Server Component. "use client" только когда нужен браузерный API/state
- Мутации — через Server Actions, не через API-роуты
- Fetch делает кэш сам — не оборачивай вручную
- next/image обязателен для картинок

## Запреты
- Никаких `getServerSideProps`, `getStaticProps` — это Pages Router
- Никаких `pages/api/*` для мутаций — только Server Actions
- Не тащи `useEffect` в Server Components

Server Component с fetch

// app/products/page.tsx
import { Product } from "@/components/Product";

export default async function ProductsPage() {
  const res = await fetch("https://api.example.com/products", {
    next: { revalidate: 60 },
  });
  const products = await res.json();

  return (
    <div className="grid grid-cols-3 gap-4">
      {products.map((p) => (
        <Product key={p.id} product={p} />
      ))}
    </div>
  );
}

next.revalidate: 60 — ISR: через минуту страница пересоберётся в фоне.

Server Action для мутаций

// app/actions/product.ts
"use server";

import { revalidatePath } from "next/cache";
import { z } from "zod";
import { prisma } from "@/lib/db";
import { auth } from "@/lib/auth";

const schema = z.object({
  name: z.string().min(1),
  priceCents: z.number().int().positive(),
});

export async function createProduct(formData: FormData) {
  const session = await auth();
  if (!session) throw new Error("Unauthorized");

  const parsed = schema.safeParse({
    name: formData.get("name"),
    priceCents: Number(formData.get("priceCents")),
  });
  if (!parsed.success) {
    return { error: parsed.error.flatten() };
  }

  await prisma.product.create({
    data: { ...parsed.data, ownerId: session.user.id },
  });

  revalidatePath("/products");
  return { ok: true };
}

Использование в форме — без JS:

// app/products/new/page.tsx
import { createProduct } from "@/app/actions/product";

export default function NewProduct() {
  return (
    <form action={createProduct} className="space-y-4">
      <input name="name" required className="border p-2" />
      <input name="priceCents" type="number" required className="border p-2" />
      <button type="submit">Создать</button>
    </form>
  );
}

Работает даже с выключенным JS.

Streaming через Suspense

// app/dashboard/page.tsx
import { Suspense } from "react";
import { Stats } from "./Stats";
import { Activity } from "./Activity";

export default function Dashboard() {
  return (
    <div>
      <h1>Дашборд</h1>
      <Suspense fallback={<p>Статистика загружается...</p>}>
        <Stats />
      </Suspense>
      <Suspense fallback={<p>Активность...</p>}>
        <Activity />
      </Suspense>
    </div>
  );
}

Клиент получает shell → по мере готовности подтягивает блоки. Ощутимый прирост perceived performance, без конфигурации.

Три уровня кэша

Next.js 15 кэширует:

  • Request Memoization — один fetch() в одном рендере не делает два запроса
  • Data Cache — между рендерами, инвалидируется по времени или тегу
  • Full Route Cache — статический HTML страниц

Ручное управление:

// Инвалидация по пути
import { revalidatePath } from "next/cache";
revalidatePath("/products");

// Инвалидация по тегу
import { revalidateTag } from "next/cache";
revalidateTag("products");

// Фетч с тегом
fetch(url, { next: { tags: ["products"] } });

// Отключить кэш
fetch(url, { cache: "no-store" });

Подводные камни

  • Client-only библиотеки в Server Components. window is not defined — переноси в клиентский компонент.
  • cookies() и headers() — async в Next.js 15. const c = await cookies() — без await будет warning.
  • Server Actions и FormData. Все значения — строки. Конвертируй вручную.
  • Claude путает Pages и App Router. Прибивай в CLAUDE.md жёстко.

Как попробовать

1. npx create-next-app@latest → выбери App Router, TypeScript 2. CLAUDE.md из сниппета выше 3. Попроси: «Server Action для создания продукта через Prisma» 4. Проверь, что модель не скатилась в pages/api/*

Канал с гайдами и контентом по claude code, выкладываем новости (когда режут лимиты в 10 раз) и какие инструменты через claude реализуем для проектов, канал: https://t.me/claudedevolper