ДокументацияReactNext.js — фреймворк для React
Средний 14 мин чтения

Next.js — фреймворк для React

Next.js: App Router, маршрутизация на основе файлов, SSR, SSG, ISR, layout, loading, error — всё для продакшн React-приложения.

Next.jsApp RouterSSRSSGISRReactфреймворк

Что такое Next.js

Next.js — фреймворк поверх React от Vercel. Добавляет серверный рендеринг, маршрутизацию на основе файлов, оптимизацию изображений и многое другое.

Создание проекта:

npx create-next-app@latest my-app --typescript --tailwind --app
cd my-app
npm run dev

App Router

Начиная с Next.js 13, основной системой маршрутизации стал App Router (папка app/). Каждый файл page.tsx в папке — это маршрут.

app/
├── layout.tsx          → Обёртка для всех страниц
├── page.tsx            → /
├── about/
│   └── page.tsx        → /about
├── blog/
│   ├── page.tsx        → /blog
│   └── [slug]/
│       └── page.tsx    → /blog/my-post
├── dashboard/
│   ├── layout.tsx      → layout только для dashboard
│   ├── page.tsx        → /dashboard
│   └── settings/
│       └── page.tsx    → /dashboard/settings
└── api/
    └── hello/
        └── route.ts    → API-маршрут /api/hello

Layout

layout.tsx — обёртка, которая не перерисовывается при навигации:

// app/layout.tsx
import './globals.css'

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="ru">
      <body>
        <nav>Мой сайт</nav>
        <main>{children}</main>
      </body>
    </html>
  )
}

Вложенные layouts — каждый сегмент URL может иметь свой layout:

// app/dashboard/layout.tsx
export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <div className="flex">
      <Sidebar />
      <div className="flex-1">{children}</div>
    </div>
  )
}

Page

// app/page.tsx
export default function HomePage() {
  return <h1>Добро пожаловать!</h1>
}

Страница может быть async — для загрузки данных на сервере:

// app/users/page.tsx
async function getUsers() {
  const res = await fetch('https://api.example.com/users', {
    cache: 'no-store',
  })
  return res.json()
}

export default async function UsersPage() {
  const users = await getUsers()

  return (
    <ul>
      {users.map((user: { id: number; name: string }) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  )
}

Динамические маршруты

app/
└── products/
    └── [id]/
        └── page.tsx    → /products/42
// app/products/[id]/page.tsx
export default async function ProductPage({
  params,
}: {
  params: Promise<{ id: string }>
}) {
  const { id } = await params
  const product = await getProduct(id)

  return <h1>{product.name}</h1>
}

Стратегии рендеринга

SSR — Server-Side Rendering

Страница рендерится на сервере при каждом запросе:

// app/products/page.tsx
async function ProductsPage() {
  const products = await fetch('https://api.example.com/products', {
    cache: 'no-store',     // Всегда свежие данные
  }).then((r) => r.json())

  return <ProductList products={products} />
}

SSG — Static Site Generation

Страница генерируется один раз при сборке:

async function ProductsPage() {
  const products = await fetch('https://api.example.com/products', {
    cache: 'force-cache',    // Кэшировать навсегда
  }).then((r) => r.json())

  return <ProductList products={products} />
}

ISR — Incremental Static Regeneration

Статика, которая обновляется в фоне через заданный интервал:

async function ProductsPage() {
  const products = await fetch('https://api.example.com/products', {
    next: { revalidate: 60 },   // Обновлять раз в 60 секунд
  }).then((r) => r.json())

  return <ProductList products={products} />
}

Сравнение

СтратегияКогда рендеритсяПодходит для
SSRПри каждом запросеЛичный кабинет, данные в реальном времени
SSGОдин раз при сборкеЛендинги, документация, блог-посты
ISRПри сборке + фон обновлениеКаталоги, списки, контент, который обновляется

Loading и Error

// app/dashboard/loading.tsx
export default function Loading() {
  return <div>Загрузка...</div>
}
// app/dashboard/error.tsx
'use client'

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  return (
    <div>
      <h2>Что-то пошло не так</h2>
      <button onClick={() => reset()}>Попробовать снова</button>
    </div>
  )
}

API Routes

// app/api/hello/route.ts
import { NextResponse } from 'next/server'

export async function GET() {
  return NextResponse.json({ message: 'Привет из API!' })
}

export async function POST(request: Request) {
  const body = await request.json()
  return NextResponse.json({ received: body }, { status: 201 })
}

Metadata — SEO

// app/page.tsx
import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: 'Мой сайт — Главная',
  description: 'Описание страницы для поисковых систем',
}

export default function HomePage() {
  return <h1>Главная</h1>
}

Динамические метаданные:

// app/products/[id]/page.tsx
export async function generateMetadata({
  params,
}: {
  params: Promise<{ id: string }>
}) {
  const { id } = await params
  const product = await getProduct(id)

  return {
    title: product.name,
    description: product.description,
  }
}

Image — оптимизация изображений

import Image from 'next/image'

function Hero() {
  return (
    <Image
      src="/hero.jpg"
      alt="Баннер"
      width={1200}
      height={600}
      priority          // Предзагрузка для LCP-изображений
    />
  )
}

Next.js автоматически конвертирует в WebP/AVIF, генерирует srcset и лениво загружает изображения ниже фолда.

import Link from 'next/link'

function Navbar() {
  return (
    <nav>
      <Link href="/">Главная</Link>
      <Link href="/about">О нас</Link>
      <Link href={`/users/${userId}`}>Профиль</Link>
    </nav>
  )
}

Link prefetch'ит страницу при появлении в viewport — переход мгновенный.

Клиентские компоненты

По умолчанию все компоненты в App Router — серверные. Для интерактивности добавьте 'use client':

'use client'

import { useState } from 'react'

export function Counter() {
  const [count, setCount] = useState(0)
  return <button onClick={() => setCount(count + 1)}>{count}</button>
}

Итог

Next.js — лучший выбор, когда React-проекту нужен SSR, SEO или сложная маршрутизация. App Router с файловой структурой, встроенная оптимизация изображений, Server Components и Server Actions закрывают большинство задач без сторонних библиотек. Для простых SPA без SEO достаточно Vite.