Що таке Next.js і чому його варто використовувати
Next.js - це фреймворк для React, який додає серверний рендеринг, маршрутизацію на основі файлів і вбудовану оптимізацію, щоб ти не налаштовував усе це вручну.
Теорія
TL;DR
- React рендерить у браузері після завантаження JS; Next.js рендерить на сервері першим, відправляє HTML, потім гідратує через React
- Структура файлів у
app/стає маршрутами автоматично, без React Router - SSR, SSG, ISR і CSR доступні для кожної сторінки окремо
- Next.js для блогів, магазинів і маркетингових сайтів де важливе SEO; чистий React для дашбордів і внутрішніх інструментів
Швидкий приклад
Різниця одразу видна в тому, як працює маршрутизація (routing):
// Чистий React - маршрутизацію налаштовуєш вручну
import { BrowserRouter, Routes, Route } from 'react-router-dom';
export default function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</BrowserRouter>
);
}
// Next.js - структура файлів і є маршрутизація
// app/page.js -> /
// app/about/page.js -> /about
// Жодного Router. Жодної конфігурації.Файл є - маршрут є.
Ключова різниця
React - це бібліотека для UI. Вона рендерить компоненти в браузері після завантаження JavaScript, тому пошукові боти бачать порожній <div> поки JS не виконається. Next.js робить навпаки: сервер запускає твій компонент, генерує справжній HTML і відправляє його в браузер. Користувач бачить контент відразу. Потім у фоні завантажується React і гідратує (hydrates) сторінку - підключає обробники подій і робить її інтерактивною. Ті ж компоненти, ті ж хуки, але перший paint відбувається на сервері, а не в браузері.
Стратегії рендерингу
Next.js дає чотири режими рендерингу, і ти обираєш для кожної сторінки окремо:
- SSR - сервер рендерить при кожному запиті. Підходить для персоналізованого контенту або даних, що змінюються постійно.
- SSG - сторінка будується один раз при деплої, роздається з CDN. Найшвидший варіант для контенту, що рідко змінюється.
- ISR - статична сторінка, яка регенерує в фоні за розкладом. CDN-швидкість плюс свіжіші дані.
- CSR - звичайний React на клієнті. Використовуй коли сторінці не потрібне SEO і вона повністю інтерактивна.
// SSR: cache: 'no-store' - кожен запит повертає свіжий рендер з сервера
export default async function Page() {
const data = await fetch('https://api.example.com/posts', {
cache: 'no-store'
});
const posts = await data.json();
return <PostList posts={posts} />;
}
// ISR: регенерує раз на годину, в проміжку роздає закешований HTML
export default async function Page() {
const data = await fetch('https://api.example.com/posts', {
next: { revalidate: 3600 }
});
const posts = await data.json();
return <PostList posts={posts} />;
}Коли використовувати
Next.js підходить коли:
- SEO важливе: блоги, магазини, маркетингові сторінки
- Швидкість першого завантаження є пріоритетом
- Потрібна серверна авторизація або персоналізація
- Хочеш маршрутизацію, API routes та оптимізацію зображень вже готовими
Чистий React підходить краще коли:
- Будуєш адмін-дашборд або внутрішній інструмент, який Google не індексує
- Весь контент генерується на клієнті (як Figma або real-time редактор)
- Робиш прототип і хочеш найпростіше налаштування
Таблиця порівняння
| Аспект | React (SPA) | Next.js |
|---|---|---|
| Маршрутизація | Вручну (react-router) | На основі файлів, автоматична |
| Початковий HTML | Порожній <div> | Повний вміст сторінки |
| SEO | Погане без налаштування | Добре з коробки |
| Перше завантаження | Чекає на JS | HTML + CSS спочатку |
| Отримання даних | useEffect на клієнті | Server Components, fetch на сервері |
| API | Немає | Route Handlers вбудовані |
| Деплой | Статичний хостинг | Node.js сервер або serverless |
| Для чого | Дашборди, внутрішні інструменти | Блоги, магазини, маркетинг |
Як це працює всередині
Коли ти запитуєш сторінку Next.js, сервер запускає твій компонент і генерує HTML. Цей HTML іде в браузер одразу. Користувач бачить контент ще до того, як будь-який JavaScript запустився. Потім React завантажується і гідратує сторінку, підключаючи обробники подій.
Для статичних сторінок (SSG/ISR) цей HTML генерується заздалегідь і роздається з CDN. Сервер не задіяний на кожному запиті, тому статичні сторінки завантажуються швидше за SSR.
Server Components - це дефолт в App Router (Next.js 13+). Вони виконуються тільки на сервері, тому можна напряму читати з бази даних без ризику розкрити ключі доступу в браузері. Коли компоненту потрібна інтерактивність - useState або onClick - додаєш 'use client' вгорі файлу.
// Server Component (за замовчуванням) - виконується тільки на сервері
export default async function UsersPage() {
const users = await db.user.findMany(); // пряме звернення до БД, без витоку ключів
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
// Client Component - потрібен 'use client' для хуків і обробників подій
'use client';
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Кліки: {count}
</button>
);
}Типові помилки
1. Fetch через useEffect замість async Server Component
Сенс Server Components у тому, що дані отримуються на сервері. Якщо ти використовуєш useEffect, ти повертаєшся до клієнтського рендерингу: порожній HTML іде спочатку, дані завантажуються після запуску JS.
// Неправильно - зводить нанівець серверний рендеринг
export default function Page() {
const [data, setData] = useState(null);
useEffect(() => {
fetch('/api/data').then(r => r.json()).then(setData);
}, []);
return <div>{data?.name}</div>; // браузер отримує порожній HTML
}
// Правильно - fetch безпосередньо в async компоненті
export default async function Page() {
const data = await fetch('https://api.example.com/data').then(r => r.json());
return <div>{data.name}</div>; // сервер відправляє повний HTML
}2. Використання хуків у Server Component
Server Components виконуються на сервері. useState, useContext, обробники подій - там не працюють. Рішення одне: 'use client' вгорі файлу.
3. Розбіжність при гідратації (hydration mismatch)
Якщо сервер рендерить HTML, що відрізняється від клієнтського, React не може правильно підключити обробники подій. Часта причина - new Date() безпосередньо в рендері.
// Неправильно - сервер рендерить '22:20:18', клієнт рендерить '22:20:25'
export default function Time() {
return <div>{new Date().toLocaleTimeString()}</div>;
}
// Правильно - час рендериться тільки на клієнті через useEffect
'use client';
import { useEffect, useState } from 'react';
export default function Time() {
const [time, setTime] = useState('');
useEffect(() => setTime(new Date().toLocaleTimeString()), []);
return <div>{time}</div>;
}4. Секретні API-ключі у файлах Server Components
Server Components виконуються на сервері, але код потрапляє в браузер через React Server Component payload. Секретні ключі видно в мережевих запитах. Тримай їх у .env.local і використовуй тільки в API routes або Server Actions, а не в компонентах.
5. Повне перебудовування сайту при кожному деплої замість ISR
10 000 сторінок товарів - це хвилини збірки при кожному деплої. Використовуй revalidate: перший запит генерує і кешує HTML, наступний проміжок часу всі запити б'ються в кеш миттєво, після інтервалу Next.js регенерує в фоні поки стара сторінка ще роздається.
Де зустрічається в реальних проектах
- TikTok, Hulu, Nike: маркетингові сайти та магазини де важливе SEO
- Stripe, GitHub: сайти з документацією
- Shopify Hydrogen: commerce-фреймворк побудований на Next.js
- Notion, Linear: Next.js для публічних сторінок, чистий React для внутрішніх інструментів
- Vercel: вся їхня платформа на Next.js, Server Components для дашборду
Додаткові питання
Q: Яка різниця між Server Components і SSR?
A: SSR - це патерн деплою де React рендерить HTML на Node.js сервері при кожному запиті. Server Components - це фіча React 18, вони виконуються на сервері і стрімлять JSX на клієнт. Next.js використовує обидва: Server Components за замовчуванням, SSR для динамічних сторінок.
Q: Якщо Next.js рендерить на сервері, як обробляти real-time оновлення?
A: Server Components рендерять один раз і повертають статичний HTML. Для real-time даних використовуй Client Components з useEffect або WebSockets. Для нечастих оновлень Server Actions з revalidatePath() запускають свіжий рендер конкретних сторінок.
Q: Коли обирати SSG замість SSR?
A: SSG будує сторінки при деплої і роздає їх з CDN - миттєво, без витрат сервера на кожен запит. SSR рендерить при кожному запиті, що повільніше і дорожче. SSG для контенту, що рідко змінюється (пости, сторінки товарів). SSR для персоналізованого контенту або даних що змінюються постійно.
Q: Що відбувається якщо в Server Component є повільний запит до БД?
A: Сторінка чекає поки запит завершиться перш ніж відправити будь-який HTML в браузер. Обертай повільні компоненти в <Suspense> - решта сторінки рендериться одразу, а повільний компонент стрімиться пізніше коли дані готові.
Q: (Senior рівень) Як побудувати сайт де частина сторінок статична, частина SSR, а частина CSR?
A: revalidate (ISR) для переважно статичних сторінок типу лістингу товарів. SSR (cache: 'no-store') для сторінок з персоналізацією або авторизацією. Client Components для повністю інтерактивних секцій. Статика найшвидша але може застарівати. SSR завжди свіже але з затримкою сервера. CSR інтерактивний але без SEO. Правильний розподіл залежить від того як часто змінюється контент і чи потрібно Google його індексувати.
Приклади
Отримання даних на сервері для сторінки блогу
// app/blog/[slug]/page.js
// Виконується на сервері. params.slug береться з сегмента URL.
export default async function BlogPost({ params }) {
const post = await fetch(
`https://api.example.com/posts/${params.slug}`
).then(r => r.json());
// Сервер генерує повністю заповнений HTML перед відправкою в браузер.
// Google бачить весь контент статті одразу.
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
);
}URL /blog/next-js-guide встановлює params.slug у "next-js-guide". Fetch виконується на сервері. HTML що приходить в браузер вже містить заголовок і текст, а не порожні місця.
ISR для сторінки товару з 10 000 продуктів
// app/products/[id]/page.js
export const revalidate = 3600; // регенерує раз на годину
export default async function Product({ params }) {
const product = await fetch(
`https://api.example.com/products/${params.id}`,
{ next: { revalidate: 3600 } }
).then(r => r.json());
return (
<div>
<h1>{product.name}</h1>
<p>${product.price}</p>
</div>
);
}Перший запит генерує і кешує HTML. Наступну годину всі запити б'ються в кеш миттєво. Після години Next.js регенерує в фоні поки стара сторінка ще роздається. Зміна ціни видима в межах години без торкання pipeline деплою.
Комбінація Server і Client Components
// app/dashboard/page.js - Server Component отримує дані
import StatsChart from './StatsChart';
export default async function Dashboard() {
const stats = await fetch('https://api.example.com/stats', {
cache: 'no-store'
}).then(r => r.json());
// Передаємо дані в Client Component, який відповідає за інтерактивність
return (
<main>
<h1>Дашборд</h1>
<StatsChart data={stats} />
</main>
);
}// app/dashboard/StatsChart.js - Client Component керує станом
'use client';
import { useState } from 'react';
export default function StatsChart({ data }) {
const [view, setView] = useState('weekly');
return (
<div>
<button onClick={() => setView('weekly')}>Тиждень</button>
<button onClick={() => setView('monthly')}>Місяць</button>
<p>Дані за {view === 'weekly' ? 'тиждень' : 'місяць'}: {data[view]}</p>
</div>
);
}Сервер робить важкий запит до даних, клієнт керує станом без зайвих мережевих запитів. Я використовував цей патерн на реальних дашбордах - початкове завантаження залишається швидким, бо користувач не чекає на JS щоб побачити свої дані.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.