Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Шаблони аутентифікації в Next.js». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Шаблони аутентифікації в Next.js** охоплюють чотири шари: Middleware (захист на edge до завантаження сторінки), Server Components (перевірка і дані за один SSR-прохід), Route Handlers (захист API-ендпоінтів), Server Actions (захист мутацій). ```tsx // Middleware - блокує до запуску будь-якого коду сторінки if (!token) { const loginUrl = new URL("/login", request.url); loginUrl.searchParams.set("callbackUrl", pathname); return NextResponse.redirect(loginUrl); } ``` **Головне правило:** завжди перевіряй на сервері. Комбінуй Middleware для широкого захисту із Server Components для отримання даних. Токени зберігай у HttpOnly cookies, не в localStorage.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Шаблони аутентифікації в Next.js** - це підходи до перевірки особи користувача на різних етапах обробки запиту: на edge (Middleware), під час рендерингу сторінки (Server Components), на рівні API (Route Handlers) та перед мутаціями (Server Actions). ## Теорія ### TL;DR - Уяви аеропорт: Middleware перевіряє всіх на вході до зони вильоту (кожен запит), Server Components перевіряють посадковий талон біля виходу на борт (рендеринг сторінки), Route Handlers перевіряють на кожній стійці реєстрації (API-виклик) - Middleware найшвидший шар, але найбільш обмежений: без запитів до БД, без Node.js-модулів - Server Components перевіряють авторизацію і отримують дані за один SSR-прохід, без зайвого клієнтського запиту - Route Handlers і Server Actions - це публічні ендпоінти, кожен потребує власного явного виклику `getSession()` - Комбінуй шари: Middleware блокує рано, Server Components отримують дані, Server Actions захищають мутації ### Швидкий приклад ```tsx // middleware.ts - захищає /dashboard/* на рівні edge import { NextResponse } from "next/server"; import type { NextRequest } from "next/server"; const publicPaths = ["/", "/login", "/register", "/api/auth"]; export function middleware(request: NextRequest) { const { pathname } = request.nextUrl; if (publicPaths.some(path => pathname.startsWith(path))) { return NextResponse.next(); // публічні шляхи пропускаємо } const token = request.cookies.get("auth-token")?.value; if (!token) { const loginUrl = new URL("/login", request.url); loginUrl.searchParams.set("callbackUrl", pathname); // зберігаємо пункт призначення return NextResponse.redirect(loginUrl); } return NextResponse.next(); } export const config = { matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"], }; ``` Middleware виконується у Edge Runtime від Vercel (V8 ізоляти), без повноцінного Node.js. Він читає заголовки і cookies через `NextRequest` і повертає відповідь до того, як запит дійде до твоїх сторінок. ### Головна різниця Middleware перехоплює запити до того, як запустився будь-який код сторінки. Це швидко, але є обмеження: немає Node.js API і прямих запитів до БД. Server Components виконуються під час SSR у Node.js і можуть звертатися до БД напряму, але лише при рендерингу конкретної сторінки. Route Handlers - це охорона на рівні окремого ендпоінта. Server Actions теж є публічними мережевими ендпоінтами і потребують власних перевірок, бо їх можна викликати напряму з клієнта. ### Коли що використовувати - Захист цілих розділів (адмінка, дашборд) - Middleware - Отримання даних користувача на конкретній сторінці - Server Component - Захист API-ендпоінтів - Route Handler - Валідація перед записом у БД - Server Action - OAuth (Google, GitHub) - Route Handler через NextAuth.js - Рольовий доступ на рівні сторінки - Server Component із `notFound()` або `redirect()` ### Таблиця порівняння | Шар | Де виконується | Запити до БД | Коли використовувати | |---|---|---|---| | Middleware | Edge (V8) | Ні | Широкий захист маршрутів | | Server Component | Node.js (SSR) | Так | Аутентифікація + дані сторінки | | Route Handler | Node.js | Так | Захист API | | Server Action | Node.js | Так | Захист мутацій | | Layout | Node.js (SSR) | Так | Аутентифікація розділу | ### Як це працює всередині Middleware запускається у Edge Runtime до маршрутизації. Він має доступ до `NextRequest` (заголовки, cookies, URL), але не може використовувати Node.js-модулі на кшталт `pg`, `fs` чи Prisma-клієнта. Server Components виконуються в Node.js під час SSR і отримують cookies через `cookies()` з `next/headers`. Авторизаційний стан вбудовується прямо в RSC payload, тому окремого запиту з клієнта не потрібно. Одна річ, яку я бачив у кількох командах: Middleware запускається на кожному відповідному запиті, включно з API-маршрутами. Якщо `matcher` занадто широкий, edge-затримка з'являється навіть на health check. ### Типові помилки **Помилка: статичні ресурси потрапляють у Middleware** ```tsx // Неправильно - аутентифікація запускається на кожному CSS, JS, зображенні export const config = { matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"], }; // Краще - захищай лише те, що потрібно export const config = { matcher: ["/dashboard/:path*", "/profile/:path*", "/admin/:path*"], }; ``` Широкий negative-lookahead-матчер є в документації Next.js для i18n-сценаріїв. Для аутентифікації явне перерахування шляхів уникає зайвих edge-витрат. **Помилка: немає `callbackUrl` при редиректі** ```tsx // Неправильно - користувач втрачає пункт призначення після входу return NextResponse.redirect(new URL("/login", req.url)); // Правильно - після входу повертаємо туди, куди йшли const loginUrl = new URL("/login", req.url); loginUrl.searchParams.set("callbackUrl", req.nextUrl.pathname); return NextResponse.redirect(loginUrl); ``` **Помилка: запити до БД у Middleware** ```tsx // Неправильно - Prisma, pg, Drizzle не працюють у edge runtime const user = await prisma.user.findUnique({ where: { id: session.sub } }); // Правильно - використовуй jose для перевірки JWT на edge import { jwtVerify } from "jose"; await jwtVerify(token, new TextEncoder().encode(process.env.JWT_SECRET!)); ``` **Помилка: перевірка аутентифікації тільки на клієнті** ```tsx // Неправильно (Client Component) - користувач може підробити це "use client"; if (!document.cookie.includes("auth-token")) router.push("/login"); // Правильно - перевіряй у Server Component або Middleware const session = await getSession(); // лише на сервері if (!session) redirect("/login"); ``` **Помилка: пропущена перевірка у Server Actions** Server Actions - це публічні ендпоінти. Виклик `updateProfile()` з клієнта проходить через ту саму мережу, що й будь-який API-маршрут. Завжди викликай `getSession()` на початку кожного Server Action, що торкається даних користувача. Ніколи не пропускай цей крок мовчки. ### Реальне використання - NextAuth.js (Auth.js v5) - Middleware і Route Handlers для OAuth, хелпер `auth()` у Server Components - Clerk - Middleware для рольового доступу, поширений у T3 Stack - Supabase - Server Components із `createServerClient()` для row-level security - Lucia - кастомні сесії через Route Handlers у open-source стартерах - Власний JWT - Middleware з бібліотекою `jose` для перевірки на edge ### Follow-up питання **Q:** Чим відрізняється Middleware від `getSession()` у Server Component? **A:** Middleware виконується на edge до завантаження сторінки і не має доступу до Node.js-модулів. `getSession()` у Server Component виконується в Node.js під час SSR і може звертатися до БД. Middleware блокує, Server Component отримує дані. **Q:** Що відбувається зі статичними сторінками при Middleware-аутентифікації? **A:** Статичні сторінки обходять Middleware, якщо не потрапляють у `matcher`. Завжди виключай `/_next/static` і `/_next/image` з auth-матчерів, інакше кожен запит статичного ресурсу йде через edge. **Q:** Як реалізувати рольовий доступ у Server Components? **A:** Отримай сесію, перевір `session.user.role`, потім викличи `redirect()` або `notFound()`. Наприклад, `if (session.role !== "admin") notFound()` повертає 404 замість того, щоб розкрити факт існування сторінки. **Q:** Чому не можна виконувати запити до БД у Middleware? **A:** Edge Runtime працює у V8 ізолятах без Node.js API. `pg`, Prisma і Drizzle залежать від Node-модулів, яких на edge немає. Використовуй JWT через `jose` або зовнішні сервіси на кшталт Upstash Redis. **Q:** Як Middleware взаємодіє з parallel routes в App Router? **A:** Middleware виконується до будь-яких intercepting routes. Parallel routes успадковують сесію від батьківського Server Component у layout. З хелпером `auth()` від Auth.js v5 сесію отримують один раз у layout і передають у дочірні слоти, уникаючи дублювання запитів. **Q:** Як перенести аутентифікацію з Pages Router до App Router? **A:** Замінюй логіку `_app.getInitialProps` на Middleware. Перевірки сесій у `getServerSideProps` переносяться до Server Components із `cookies()` з `next/headers`. Патерн `getServerSession()` відповідає `auth()` у Auth.js v5. ## Приклади ### Middleware із збереженням callbackUrl ```tsx // 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")?.value; const isLoginPage = request.nextUrl.pathname.startsWith("/login"); if (!token && !isLoginPage) { const loginUrl = new URL("/login", request.url); loginUrl.searchParams.set("callbackUrl", request.nextUrl.pathname); return NextResponse.redirect(loginUrl); } return NextResponse.next(); } export const config = { matcher: ["/dashboard/:path*", "/profile/:path*", "/admin/:path*"], }; ``` Явний matcher захищає три розділи без зайвих витрат на статичні ресурси. Після входу застосунок читає `callbackUrl` із query string і повертає користувача туди, куди він хотів потрапити. ### Server Component із перевіркою і отриманням даних за один прохід ```tsx // lib/auth.ts import { cookies } from "next/headers"; export async function getSession() { const cookieStore = await cookies(); const token = cookieStore.get("auth-token")?.value; if (!token) return null; try { return await verifyToken(token); // повертає об'єкт користувача } catch { return null; } } // app/dashboard/page.tsx import { redirect } from "next/navigation"; import { getSession } from "@/lib/auth"; export default async function DashboardPage() { const session = await getSession(); if (!session) redirect("/login"); const stats = await fetchUserStats(session.user.id); // один прохід, без зайвого запиту return ( <div> <h1>Ласкаво просимо, {session.user.name}</h1> <StatsPanel data={stats} /> </div> ); } ``` Server Component перевіряє авторизацію і отримує дані сторінки за один прохід. Жодного клієнтського round-trip, жодного стану завантаження для auth-перевірки. ### NextAuth.js v5 із власним Middleware ```tsx // auth.ts import NextAuth from "next-auth"; import GitHub from "next-auth/providers/github"; import Credentials from "next-auth/providers/credentials"; export const { auth, handlers, signIn, signOut } = NextAuth({ providers: [ GitHub, Credentials({ credentials: { email: { label: "Email", type: "email" }, password: { label: "Password", type: "password" }, }, authorize: async (credentials) => { const user = await getUserByEmail(credentials.email as string); if (!user) return null; const valid = await verifyPassword(credentials.password as string, user.password); return valid ? user : null; }, }), ], }); // app/api/auth/[...nextauth]/route.ts import { handlers } from "@/auth"; export const { GET, POST } = handlers; // middleware.ts - Auth.js v5 надає власну Middleware-функцію export { auth as middleware } from "@/auth"; export const config = { matcher: ["/dashboard/:path*"], }; ``` Auth.js v5 експортує Middleware-функцію, яку можна використовувати напряму або обгорнути для власної логіки. Хелпер `auth()` однаково працює в Server Components і Server Actions без додаткового налаштування. ### Server Action із перевіркою авторизації ```tsx "use server"; import { getSession } from "@/lib/auth"; import { revalidatePath } from "next/cache"; export async function updateProfile(formData: FormData) { const session = await getSession(); if (!session) { throw new Error("Unauthorized"); // ніколи не мовчи при помилці } const name = formData.get("name") as string; await db.update(users) .set({ name }) .where(eq(users.id, session.user.id)); revalidatePath("/profile"); } ``` Server Actions - це публічні мережеві ендпоінти. Без перевірки `getSession()` на початку будь-який запит, що має вигляд валідного виклику, може запустити мутацію.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.