Next.js — это React-фреймворк от Vercel. Если React — это движок, то Next.js — это готовый автомобиль: маршрутизация из коробки, серверный рендеринг, оптимизация изображений, API-маршруты и многое другое.
Что добавляет Next.js к React:
next/image)next/font)Next.js имеет два роутера:
| | Pages Router | App Router (актуальный) |
|---|---|---|
| Папка | /pages | /app |
| Данные | getServerSideProps / getStaticProps | async Server Components |
| Компоненты | Всё клиентское | Server Components по умолчанию |
| Версия | До Next.js 12 | Next.js 13+ (рекомендуется) |
Ключевое нововведение Next.js 13+ (App Router):
// Server Component (по умолчанию в /app) — рендерится на сервере
// Может: async/await, обращаться к БД напрямую, читать файлы
// Не может: useState, useEffect, обработчики событий, browser API
async function UserProfile({ id }) {
// Прямой запрос к БД — без fetch, без useEffect, без loading state!
const user = await db.users.findById(id)
return (
<div>
<h1>{user.name}</h1>
<InteractiveButton userId={id} /> {/* клиентский компонент */}
</div>
)
}
// Client Component — 'use client' директива вверху файла
'use client'
function InteractiveButton({ userId }) {
const [liked, setLiked] = useState(false) // useState — только в клиентском!
return <button onClick={() => setLiked(!liked)}>
{liked ? 'Понравилось' : 'Нравится'}
</button>
}app/
page.tsx -> /
about/
page.tsx -> /about
blog/
page.tsx -> /blog
[slug]/
page.tsx -> /blog/любой-slug
dashboard/
layout.tsx -> общий layout для /dashboard/*
page.tsx -> /dashboard
settings/
page.tsx -> /dashboard/settings// app/blog/[slug]/page.tsx
export default async function BlogPost({ params }) {
const { slug } = params // URL параметр из папки [slug]
const post = await fetchPost(slug)
return <article>{post.content}</article>
}
// Статическая генерация: список всех возможных slug
export async function generateStaticParams() {
const posts = await fetchAllPosts()
return posts.map(post => ({ slug: post.slug }))
}CSR (Client-Side Rendering) — React по умолчанию:
SSR (Server-Side Rendering) — данные на сервере:
SSG (Static Site Generation) — генерация при сборке:
// Pages Router (устаревший, но важно знать)
// SSR: данные на каждый запрос
export async function getServerSideProps(context) {
const user = await fetchUser(context.params.id)
return { props: { user } }
}
// SSG: данные при сборке
export async function getStaticProps() {
const posts = await fetchPosts()
return { props: { posts }, revalidate: 60 } // ISR: обновление каждые 60с
}Используйте Next.js когда нужны:
Используйте plain React (Vite) когда:
Сравнение CSR vs SSR: клиентский рендеринг с fetch в useEffect против серверного рендеринга с данными сразу в пропсах
// Показываем разницу между CSR и SSR на уровне JavaScript.
// Симулируем задержку сети и разные стратегии рендеринга.
// --- Симуляция API ---
const fakeDB = {
users: [
{ id: 1, name: 'Алексей Иванов', role: 'Разработчик', city: 'Москва' },
{ id: 2, name: 'Мария Петрова', role: 'Дизайнер', city: 'СПб' },
]
}
function simulateApiCall(userId, delay = 500) {
return new Promise(resolve => {
setTimeout(() => {
resolve(fakeDB.users.find(u => u.id === userId))
}, delay)
})
}
// --- CSR: Client-Side Rendering ---
// (как React без Next.js, данные через useEffect + fetch)
async function renderCSR(userId) {
console.log('=== CSR (Client-Side Rendering) ===')
const startTime = performance.now()
// 1. Браузер получает пустой HTML сразу
const ms = () => Math.round(performance.now() - startTime)
console.log('[' + ms() + 'мс] HTML получен: <div id="root"></div>')
console.log('[' + ms() + 'мс] Загрузка JS бандла...')
// Симуляция загрузки JS
await new Promise(r => setTimeout(r, 200))
console.log('[' + ms() + 'мс] JS загружен, React монтируется')
console.log('[' + ms() + 'мс] Пользователь видит: [ЗАГРУЗКА...]')
// useEffect запускается после монтирования
console.log('[' + ms() + 'мс] useEffect: запускаем fetch...')
const user = await simulateApiCall(userId, 500)
console.log('[' + ms() + 'мс] Данные получены, ре-рендер')
console.log('[' + ms() + 'мс] Пользователь видит: ' + user.name + ', ' + user.role)
return {
totalTime: Math.round(performance.now() - startTime),
strategy: 'CSR',
contentVisibleAt: 700 // мс до появления контента
}
}
// --- SSR: Server-Side Rendering ---
// (как Next.js getServerSideProps или Server Components)
async function renderSSR(userId) {
console.log('
=== SSR (Server-Side Rendering) ===')
const startTime = performance.now()
// 1. Сервер получает запрос и СРАЗУ делает fetch
const ms2 = () => Math.round(performance.now() - startTime)
console.log('[' + ms2() + 'мс] Сервер получил запрос')
console.log('[' + ms2() + 'мс] Сервер делает запрос к БД...')
const user = await simulateApiCall(userId, 100) // БД на том же сервере — быстрее!
console.log('[' + ms2() + 'мс] БД ответила, рендерим HTML')
// HTML уже содержит данные!
const html = '<div class="profile"><h1>' + user.name + '</h1><p>' + user.role + ' • ' + user.city + '</p></div>'
console.log('[' + ms2() + 'мс] HTML с данными отправлен браузеру')
console.log('[' + ms2() + 'мс] Пользователь СРАЗУ видит: ' + user.name + ', ' + user.role)
// Next.js Server Component (App Router):
// async function UserProfile({ id }) {
// const user = await db.users.findById(id) // прямо к БД!
// return <div><h1>{user.name}</h1></div>
// }
return {
totalTime: Math.round(performance.now() - startTime),
strategy: 'SSR',
contentVisibleAt: 150
}
}
// --- Запуск и сравнение ---
async function compare() {
const csrResult = await renderCSR(1)
const ssrResult = await renderSSR(1)
console.log('
=== Итоги ===')
console.log('CSR: контент появился через ~' + csrResult.contentVisibleAt + 'мс')
console.log('SSR: контент появился через ~' + ssrResult.contentVisibleAt + 'мс')
console.log('SSR быстрее для первой загрузки на ~' + (csrResult.contentVisibleAt - ssrResult.contentVisibleAt) + 'мс')
console.log('
CSR лучше для: дашборды, SPA, авторизованные страницы')
console.log('SSR лучше для: лендинги, блоги, e-commerce (SEO + скорость)')
}
compare()Next.js — это React-фреймворк от Vercel. Если React — это движок, то Next.js — это готовый автомобиль: маршрутизация из коробки, серверный рендеринг, оптимизация изображений, API-маршруты и многое другое.
Что добавляет Next.js к React:
next/image)next/font)Next.js имеет два роутера:
| | Pages Router | App Router (актуальный) |
|---|---|---|
| Папка | /pages | /app |
| Данные | getServerSideProps / getStaticProps | async Server Components |
| Компоненты | Всё клиентское | Server Components по умолчанию |
| Версия | До Next.js 12 | Next.js 13+ (рекомендуется) |
Ключевое нововведение Next.js 13+ (App Router):
// Server Component (по умолчанию в /app) — рендерится на сервере
// Может: async/await, обращаться к БД напрямую, читать файлы
// Не может: useState, useEffect, обработчики событий, browser API
async function UserProfile({ id }) {
// Прямой запрос к БД — без fetch, без useEffect, без loading state!
const user = await db.users.findById(id)
return (
<div>
<h1>{user.name}</h1>
<InteractiveButton userId={id} /> {/* клиентский компонент */}
</div>
)
}
// Client Component — 'use client' директива вверху файла
'use client'
function InteractiveButton({ userId }) {
const [liked, setLiked] = useState(false) // useState — только в клиентском!
return <button onClick={() => setLiked(!liked)}>
{liked ? 'Понравилось' : 'Нравится'}
</button>
}app/
page.tsx -> /
about/
page.tsx -> /about
blog/
page.tsx -> /blog
[slug]/
page.tsx -> /blog/любой-slug
dashboard/
layout.tsx -> общий layout для /dashboard/*
page.tsx -> /dashboard
settings/
page.tsx -> /dashboard/settings// app/blog/[slug]/page.tsx
export default async function BlogPost({ params }) {
const { slug } = params // URL параметр из папки [slug]
const post = await fetchPost(slug)
return <article>{post.content}</article>
}
// Статическая генерация: список всех возможных slug
export async function generateStaticParams() {
const posts = await fetchAllPosts()
return posts.map(post => ({ slug: post.slug }))
}CSR (Client-Side Rendering) — React по умолчанию:
SSR (Server-Side Rendering) — данные на сервере:
SSG (Static Site Generation) — генерация при сборке:
// Pages Router (устаревший, но важно знать)
// SSR: данные на каждый запрос
export async function getServerSideProps(context) {
const user = await fetchUser(context.params.id)
return { props: { user } }
}
// SSG: данные при сборке
export async function getStaticProps() {
const posts = await fetchPosts()
return { props: { posts }, revalidate: 60 } // ISR: обновление каждые 60с
}Используйте Next.js когда нужны:
Используйте plain React (Vite) когда:
Сравнение CSR vs SSR: клиентский рендеринг с fetch в useEffect против серверного рендеринга с данными сразу в пропсах
// Показываем разницу между CSR и SSR на уровне JavaScript.
// Симулируем задержку сети и разные стратегии рендеринга.
// --- Симуляция API ---
const fakeDB = {
users: [
{ id: 1, name: 'Алексей Иванов', role: 'Разработчик', city: 'Москва' },
{ id: 2, name: 'Мария Петрова', role: 'Дизайнер', city: 'СПб' },
]
}
function simulateApiCall(userId, delay = 500) {
return new Promise(resolve => {
setTimeout(() => {
resolve(fakeDB.users.find(u => u.id === userId))
}, delay)
})
}
// --- CSR: Client-Side Rendering ---
// (как React без Next.js, данные через useEffect + fetch)
async function renderCSR(userId) {
console.log('=== CSR (Client-Side Rendering) ===')
const startTime = performance.now()
// 1. Браузер получает пустой HTML сразу
const ms = () => Math.round(performance.now() - startTime)
console.log('[' + ms() + 'мс] HTML получен: <div id="root"></div>')
console.log('[' + ms() + 'мс] Загрузка JS бандла...')
// Симуляция загрузки JS
await new Promise(r => setTimeout(r, 200))
console.log('[' + ms() + 'мс] JS загружен, React монтируется')
console.log('[' + ms() + 'мс] Пользователь видит: [ЗАГРУЗКА...]')
// useEffect запускается после монтирования
console.log('[' + ms() + 'мс] useEffect: запускаем fetch...')
const user = await simulateApiCall(userId, 500)
console.log('[' + ms() + 'мс] Данные получены, ре-рендер')
console.log('[' + ms() + 'мс] Пользователь видит: ' + user.name + ', ' + user.role)
return {
totalTime: Math.round(performance.now() - startTime),
strategy: 'CSR',
contentVisibleAt: 700 // мс до появления контента
}
}
// --- SSR: Server-Side Rendering ---
// (как Next.js getServerSideProps или Server Components)
async function renderSSR(userId) {
console.log('
=== SSR (Server-Side Rendering) ===')
const startTime = performance.now()
// 1. Сервер получает запрос и СРАЗУ делает fetch
const ms2 = () => Math.round(performance.now() - startTime)
console.log('[' + ms2() + 'мс] Сервер получил запрос')
console.log('[' + ms2() + 'мс] Сервер делает запрос к БД...')
const user = await simulateApiCall(userId, 100) // БД на том же сервере — быстрее!
console.log('[' + ms2() + 'мс] БД ответила, рендерим HTML')
// HTML уже содержит данные!
const html = '<div class="profile"><h1>' + user.name + '</h1><p>' + user.role + ' • ' + user.city + '</p></div>'
console.log('[' + ms2() + 'мс] HTML с данными отправлен браузеру')
console.log('[' + ms2() + 'мс] Пользователь СРАЗУ видит: ' + user.name + ', ' + user.role)
// Next.js Server Component (App Router):
// async function UserProfile({ id }) {
// const user = await db.users.findById(id) // прямо к БД!
// return <div><h1>{user.name}</h1></div>
// }
return {
totalTime: Math.round(performance.now() - startTime),
strategy: 'SSR',
contentVisibleAt: 150
}
}
// --- Запуск и сравнение ---
async function compare() {
const csrResult = await renderCSR(1)
const ssrResult = await renderSSR(1)
console.log('
=== Итоги ===')
console.log('CSR: контент появился через ~' + csrResult.contentVisibleAt + 'мс')
console.log('SSR: контент появился через ~' + ssrResult.contentVisibleAt + 'мс')
console.log('SSR быстрее для первой загрузки на ~' + (csrResult.contentVisibleAt - ssrResult.contentVisibleAt) + 'мс')
console.log('
CSR лучше для: дашборды, SPA, авторизованные страницы')
console.log('SSR лучше для: лендинги, блоги, e-commerce (SEO + скорость)')
}
compare()Создай компонент App имитирующий Next.js страницу с загрузкой данных. Используй useEffect для "получения" данных при монтировании (имитируй задержку через setTimeout). Пока данные загружаются — показывай спиннер, после — карточки постов. Данные хардкодь прямо в компоненте как константу.
useState(null) для начального состояния. setTimeout с 1500мс имитирует загрузку. setPosts(MOCK_POSTS) и setLoading(false) в колбэке. Условие загрузки: if (loading). Рендер списка: posts.map(post => <PostCard key={post.id} ... />).