Ключевые особенности Next.js
На собеседовании этот вопрос проверяет, насколько глубоко ты знаешь фреймворк, а не просто слышал о нем. Недостаточно перечислить "SSR, роутинг, оптимизация". Нужно понимать, как каждая фича работает и зачем она нужна.
Разберем основные особенности Next.js по порядку.
Гибридный рендеринг
Главная сила Next.js в том, что ты сам выбираешь стратегию рендеринга для каждой страницы. В одном приложении могут сосуществовать страницы с разными подходами:
- SSG статика, генерируется при сборке. Для страниц, которые меняются редко.
- SSR рендерится на сервере при каждом запросе. Для динамического контента.
- ISR статика с фоновым обновлением. Компромисс между SSG и SSR.
- CSR рендеринг на клиенте, как в обычном React.
// Страница "О компании" на Hack Frontend — статика (SSG по умолчанию)
export default async function AboutPage() {
const info = await fetch('https://api.hackfrontend.com/about')
const data = await info.json()
return <About data={data} />
}
// Лента задач — обновляется каждые 5 минут (ISR)
export default async function ProblemsPage() {
const res = await fetch('https://api.hackfrontend.com/problems', {
next: { revalidate: 300 }
})
const problems = await res.json()
return <ProblemList problems={problems} />
}
На собеседовании:
Часто спрашивают: "Когда бы ты выбрал SSR вместо SSG?". Хороший ответ: SSR для страниц с персонализированным контентом (профиль, дашборд), SSG для контента, одинакового для всех (блог, документация).
App Router и файловая маршрутизация
Начиная с версии 13, Next.js использует App Router. Структура папок в директории app напрямую определяет маршруты приложения:
Каждая папка это сегмент URL. Специальные файлы имеют конкретные роли:
| Файл | Назначение |
|---|---|
page.tsx | UI страницы |
layout.tsx | Общая обертка для страницы и вложенных роутов |
loading.tsx | UI загрузки (работает с Suspense) |
error.tsx | UI ошибки (работает с Error Boundary) |
not-found.tsx | UI для 404 |
// app/docs/[slug]/page.tsx
// Динамический роут: /docs/javascript, /docs/react и т.д.
export default async function DocPage({
params
}: {
params: { slug: string }
}) {
const doc = await getDocument(params.slug)
return <article>{doc.content}</article>
}
Server Components
В App Router компоненты по умолчанию серверные. Это значит:
- Они выполняются только на сервере
- Их код не попадает в клиентский бандл
- Они могут напрямую обращаться к базе данных, файловой системе, env-переменным
// Серверный компонент — код не отправляется в браузер
import { db } from '@/lib/db'
export default async function UserStats() {
const totalUsers = await db.user.count()
const totalProblems = await db.problem.count()
return (
<div>
<p>Пользователей на Hack Frontend: {totalUsers}</p>
<p>Задач: {totalProblems}</p>
</div>
)
}
Когда нужна интерактивность, помечаешь компонент как клиентский:
'use client'
import { useState } from 'react'
export default function ThemeToggle() {
const [dark, setDark] = useState(false)
return (
<button onClick={() => setDark(!dark)}>
{dark ? 'Светлая тема' : 'Темная тема'}
</button>
)
}
Важно:
Серверные компоненты не могут использовать useState, useEffect, onClick и другие клиентские API. Если нужна интерактивность, выделяй ее в отдельный клиентский компонент.
Server Actions
Server Actions позволяют вызывать серверные функции напрямую из компонентов, без создания API-эндпоинтов:
// actions/subscribe.ts
'use server'
import { db } from '@/lib/db'
export async function subscribe(email: string) {
await db.subscriber.create({
data: { email }
})
}
// Клиентский компонент вызывает серверную функцию
'use client'
import { subscribe } from '@/actions/subscribe'
export default function SubscribeForm() {
return (
<form action={async (formData) => {
const email = formData.get('email') as string
await subscribe(email)
}}>
<input name="email" type="email" placeholder="Email" />
<button type="submit">Подписаться на Hack Frontend</button>
</form>
)
}
Вложенные макеты (Nested Layouts)
Макеты в Next.js сохраняют состояние между переходами. Если у тебя есть сайдбар или навигация, они не перерендериваются при переходе между страницами:
// app/docs/layout.tsx
// Этот макет оборачивает все страницы /docs/*
export default function DocsLayout({
children
}: {
children: React.ReactNode
}) {
return (
<div className="flex">
<Sidebar />
<main className="flex-1">{children}</main>
</div>
)
}
При переходе с /docs/javascript на /docs/react макет с сайдбаром останется на месте, перерисуется только содержимое children.
Встроенная оптимизация
Компонент Image
Автоматически оптимизирует изображения: ресайзит, конвертирует в WebP/AVIF, добавляет lazy loading:
import Image from 'next/image'
export default function Avatar() {
return (
<Image
src="/avatar.png"
width={64}
height={64}
alt="Аватар пользователя"
/>
)
}
Компонент Link
Предзагружает страницу в фоне, когда ссылка попадает в область видимости. Переход становится практически мгновенным:
import Link from 'next/link'
export default function Nav() {
return (
<nav>
<Link href="/problems">Задачи</Link>
<Link href="/docs">Документация</Link>
</nav>
)
}
Оптимизация шрифтов
next/font загружает шрифты без сдвига макета (layout shift) и без запросов к Google Fonts на клиенте:
import { Inter } from 'next/font/google'
const inter = Inter({ subsets: ['latin', 'cyrillic'] })
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html className={inter.className}>
<body>{children}</body>
</html>
)
}
Middleware
Middleware выполняется до обработки запроса и позволяет перенаправлять, переписывать URL или модифицировать заголовки:
// middleware.ts (в корне проекта)
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const locale = request.cookies.get('locale')?.value || 'ru'
if (!request.nextUrl.pathname.startsWith(`/${locale}`)) {
return NextResponse.redirect(
new URL(`/${locale}${request.nextUrl.pathname}`, request.url)
)
}
}
export const config = {
matcher: ['/((?!api|_next|favicon).*)']
}
Встроенная поддержка метаданных
Next.js предоставляет удобный API для управления SEO-метаданными:
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Задачи по JavaScript — Hack Frontend',
description: 'Решай задачи из реальных собеседований по фронтенд-разработке',
openGraph: {
title: 'Задачи по JavaScript',
description: 'Решай задачи из реальных собеседований',
type: 'website'
}
}
export default function ProblemsPage() {
return <ProblemsList />
}
Полная картина
Как отвечать на собеседовании:
Не перечисляй все фичи списком. Выбери 3-4 ключевых и объясни, как они связаны между собой. Например: "Next.js использует App Router с файловой маршрутизацией, где компоненты по умолчанию серверные, что уменьшает клиентский бандл и дает доступ к данным без API-слоя через Server Actions".
Полезные ресурсы
- nextjs.org/docs — официальная документация Next.js
- nextjs.org/learn — интерактивный курс от Vercel
- nextjs.org/blog — блог с анонсами новых версий