7. Статическая генерация (SSG)
🏗️ Статическая генерация (SSG) в Next.js
Заголовок раздела «🏗️ Статическая генерация (SSG) в Next.js»Static Site Generation — это когда HTML генерируется один раз при сборке проекта и раздаётся всем пользователям как статический файл. Представь: ты испёк 1000 пирогов заранее и раздаёшь их без готовки — вместо того чтобы готовить каждый раз по заказу! 🥧
Обычный SSR: SSG:────────────────── ────────────────────────────────Запрос → Сервер При сборке (build time):→ загружает данные → generateStaticParams()→ рендерит HTML → fetch/db запросы→ отправляет клиенту → рендеринг HTML→ 200ms для каждого → сохранение .html файлов
Запрос → CDN → отдаёт .html файл → 5ms для каждого ⚡🔧 Как работает SSG в App Router
Заголовок раздела «🔧 Как работает SSG в App Router»В App Router компонент статичен по умолчанию, если не использует динамические API:
// app/about/page.tsx — автоматически SSG!// Нет cookies(), headers(), searchParams → статическая страница
export default async function AboutPage() { // Этот fetch выполнится ОДИН РАЗ при сборке const team = await fetch('https://api.example.com/team', { cache: 'force-cache', // или next: { revalidate: false } }).then(r => r.json());
return ( <main> <h1>О нас</h1> <TeamList members={team} /> </main> );}// → Генерирует /about.html при npm run build// → Хранится на CDN, отдаётся мгновенно📋 generateStaticParams: Динамические страницы → Статика
Заголовок раздела «📋 generateStaticParams: Динамические страницы → Статика»Для динамических роутов нужно указать, какие params генерировать:
interface Props { params: Promise<{ slug: string }>;}
// Говорим Next.js: сгенерируй эти страницы при сборкеexport async function generateStaticParams() { // Запрашиваем все возможные slug при сборке const posts = await fetch('https://api.blog.com/posts').then(r => r.json());
return posts.map((post: { slug: string }) => ({ slug: post.slug, })); // Например: [{ slug: 'hello-world' }, { slug: 'next-js-guide' }, ...]}
export default async function BlogPost({ params }: Props) { const { slug } = await params;
// Этот fetch тоже выполнится при сборке (для каждого slug) const post = await fetch(`https://api.blog.com/posts/${slug}`, { cache: 'force-cache', }).then(r => r.json());
if (!post) notFound();
return ( <article> <h1>{post.title}</h1> <time>{post.publishedAt}</time> <div dangerouslySetInnerHTML={{ __html: post.content }} /> </article> );}Несколько параметров:
export async function generateStaticParams() { const languages = ['ru', 'en', 'de']; const posts = await getAllPosts();
// Возвращаем все комбинации lang + slug return languages.flatMap(lang => posts.map(post => ({ lang, slug: post.slug, })) ); // [{ lang: 'ru', slug: 'hello' }, { lang: 'en', slug: 'hello' }, ...]}🔀 dynamicParams: Что делать с неизвестными params?
Заголовок раздела «🔀 dynamicParams: Что делать с неизвестными params?»// По умолчанию: true — неизвестные slug генерируются динамически// Установи false — 404 для неизвестных slugexport const dynamicParams = false; // или true (default)
export async function generateStaticParams() { return [ { slug: 'hello-world' }, { slug: 'about-nextjs' }, ];}
export default async function BlogPost({ params }: Props) { // ...}
// При dynamicParams = false:// /blog/hello-world → ✅ статическая страница// /blog/new-post → ❌ 404 (не в generateStaticParams)
// При dynamicParams = true (default):// /blog/hello-world → ✅ статическая (из кеша)// /blog/new-post → ✅ рендерится динамически, затем кешируется📊 Как это выглядит при сборке
Заголовок раздела «📊 Как это выглядит при сборке»$ npm run build
Route (app) Size First Load JS┌ ○ / 142 B 87.1 kB├ ○ /about 180 B 87.1 kB├ ● /blog 320 B 87.3 kB├ ● /blog/[slug] 275 B 87.3 kB│ ├ /blog/hello-world│ ├ /blog/nextjs-guide│ └ /blog/typescript-tips└ ○ /contact 200 B 87.1 kB
○ (Static) prerendered as static content● (SSG) prerendered as static HTML (uses generateStaticParams)ƒ (Dynamic) server-rendered on demand
✓ Generating static pages (8/8)✓ Collecting page data✓ Finalizing page optimization⏰ Когда использовать SSG?
Заголовок раздела «⏰ Когда использовать SSG?»// ✅ ОТЛИЧНО для SSG:
// 1. Блог / статьи — контент редко меняетсяexport async function generateStaticParams() { const posts = await cms.getPosts(); // Обычно десятки/сотни постов return posts.map(p => ({ slug: p.slug }));}
// 2. Документация — очень стабильный контентexport async function generateStaticParams() { const docs = await getAllDocs(); return docs.map(d => ({ slug: d.path.split('/') }));}
// 3. Страницы товаров (e-commerce) — тысячи товаровexport async function generateStaticParams() { const products = await db.product.findMany({ select: { slug: true }, where: { published: true }, }); return products.map(p => ({ slug: p.slug }));}
// 4. Лендинги, маркетинговые страницы// ✅ Максимальная производительность
// 5. Страницы профилей (если их немного)export async function generateStaticParams() { const users = await db.user.findMany({ select: { username: true }, where: { public: true }, take: 1000, // Ограничиваем при сборке }); return users.map(u => ({ username: u.username }));}// ❌ НЕ подходит для SSG:
// 1. Персонализированный контент (зависит от пользователя)// 2. Данные, меняющиеся чаще чем раз в минуту// 3. Страницы с авторизацией (корзина, профиль с приватными данными)// 4. Реальный-тайм данные (чаты, live трекинг)// 5. Поисковые результаты (infinite combinations)📊 Сравнение: SSG vs SSR vs ISR vs CSR
Заголовок раздела «📊 Сравнение: SSG vs SSR vs ISR vs CSR»| Аспект | SSG | SSR | ISR | CSR |
|---|---|---|---|---|
| Когда рендерит | При сборке | При запросе | При сборке + периодически | В браузере |
| Скорость | ⚡⚡⚡ | ⚡⚡ | ⚡⚡⚡ | ⚡ |
| Свежесть данных | При сборке | Всегда свежие | Настраивается | Всегда свежие |
| SEO | ✅✅ | ✅✅ | ✅✅ | ❌ |
| Нагрузка на сервер | Минимальная | Высокая | Низкая | Минимальная |
| CDN-friendy | ✅✅ | ❌ | ✅✅ | ✅ |
| Деплой | Простой | Нужен сервер | Нужен сервер | Любой |
🚀 Оптимизация SSG: Стратегии
Заголовок раздела «🚀 Оптимизация SSG: Стратегии»// 1. Генерация только популярных страниц при сборке// Остальные — по требованию (dynamicParams = true)export async function generateStaticParams() { // Только топ-100 самых популярных постов const posts = await db.post.findMany({ orderBy: { views: 'desc' }, take: 100, select: { slug: true }, });
return posts.map(p => ({ slug: p.slug }));}// Новые посты: генерируются при первом запросе и кешируются
// 2. Параллельная генерация данныхexport async function generateStaticParams() { // Запускаем все источники данных параллельно const [enPosts, ruPosts, dePosts] = await Promise.all([ getPostsByLang('en'), getPostsByLang('ru'), getPostsByLang('de'), ]);
return [ ...enPosts.map(p => ({ lang: 'en', slug: p.slug })), ...ruPosts.map(p => ({ lang: 'ru', slug: p.slug })), ...dePosts.map(p => ({ lang: 'de', slug: p.slug })), ];}
// 3. Частичная предгенерация + ISRexport const revalidate = 3600; // Обновлять статику каждый час
export async function generateStaticParams() { // Генерируем при сборке return await getPopularSlugs();}// Остальные — на лету, с кешем на 1 час🔧 Конфигурация SSG: export
Заголовок раздела «🔧 Конфигурация SSG: export»// Экспорт статического сайта (без сервера!)const nextConfig = { output: 'export', // Генерирует static HTML/CSS/JS // trailingSlash: true, // /about → /about/index.html};
// При npm run build создаст папку 'out' со статическими файлами!// Можно деплоить на GitHub Pages, S3, Netlify без сервера
// Ограничения output: 'export':// ❌ API Routes (нет сервера)// ❌ Server Actions// ❌ Middleware (кроме edge)// ❌ ISR (нет сервера для регенерации)// ✅ SSG → ✅// ✅ Client Components → ✅🎯 Резюме урока
Заголовок раздела «🎯 Резюме урока»- SSG по умолчанию — компонент без динамических API = статичный
- generateStaticParams — список params для предгенерации
- dynamicParams —
false= 404 для неизвестных,true= динамический рендеринг - cache: ‘force-cache’ — все fetch() кешируются при сборке
- output: ‘export’ — полностью статический сайт (без сервера)
- Идеально для: блоги, документация, лендинги, каталоги товаров