Представьте: вы запустили приложение на Next.js, работает быстро, переходы мгновенные. Кажется, всё идеально. Но через месяц Google показывает: ноль индексированных страниц. Всё потому, что всё живёт в клиентском JavaScript.
Я сам прошел этот путь. Начинал с React, где всё крутится вокруг useRouter и push. Это удобно, пока не узнаешь, что поисковые роботы не выполняют JavaScript так, как браузер пользователя.
> ПРОБЛЕМА:
Когда я только начал работать с Next.js 13 и App Router, меня смущала одна вещь: почему нельзя просто управлять состоянием маршрута через хуки, как в обычном React? Я привык к useRouter и push, и переход к серверному роутингу казался шагом назад.
Проблема в том, что клиентский роутинг работает только для людей с включенным JavaScript. Боты поисковиков видят пустые страницы или не ждут выполнения кода. Результат — контент не индексируется, сайт теряет органический трафик.
Я пытался обойти это через next/link и useEffect, но это костыли. Клиентская навигация не меняет URL на сервере, поэтому каждый переход — это новый запрос с нулевым кешем.
Три дня я боролся с тем, что мои страницы не индексировались. Я пробовал добавить meta теги динамически, но они не попадали в исходный HTML. Я пробовал Server Components, но не понимал, как управлять маршрутизацией.
В конце концов, мне пришлось признать: старые привычки мешают. Нужно было перестать думать о роутинге как о клиентском состоянии и начать воспринимать его как часть архитектуры приложения.
Ключевой инсайт: URL — это не просто переменная состояния, это идентификатор ресурса, который должен работать на сервере.
> РЕШЕНИЕ:
Переломный момент наступил, когда я начал использовать App Router правильно. Вместо useRouter().push() я научился работать with файловой системой и маршрутизацией через папки и файлы.
Основная идея: каждый маршрут — это папка с page.tsx внутри. URL строится автоматически на основе структуры директорий. Не нужно программно указывать пути — файловая система делает это за вас.
Давайте начнем с базового примера структуры директорий:
app/
├── page.tsx # /
├── about/
│ └── page.tsx # /about
├── blog/
│ ├── page.tsx # /blog
│ └── [slug]/
│ └── page.tsx # /blog/[slug]
└── shop/
├── page.tsx # /shop
└── [category]/
└── [product]/
└── page.tsx # /shop/[category]/[product]
Символ [slug] означает динамический сегмент. Это параметр, который попадает в props страницы как объект с данными запроса.
Динамические маршруты
Для работы с динамическими параметрами используется компонент page.tsx внутри папки с квадратными скобками. Параметр маршрута доступен через props:
// app/blog/[slug]/page.tsx
export default function BlogPost({ params }: { params: { slug: string } }) {
return (
<article>
<h1>Post: {params.slug}</h1>
{/* Контент поста */}
</article>
)
}
Обратите внимание на типизацию — params — это объект, где ключи соответствуют именам динамических сегментов. Это типобезопасно, TypeScript подскажет, если вы напутаете.
Данные на сервере
Для загрузки данных используйте async/await прямо в компоненте. Это серверный код, который выполняется до рендера HTML:
// app/blog/[slug]/page.tsx
async function getPost(slug: string) {
const res = await fetch(`https://api.example.com/posts/${slug}`)
return res.json()
}
export default async function BlogPost({ params }: { params: { slug: string } }) {
const post = await getPost(params.slug)
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
)
}
Серверные компоненты по умолчанию — это не просто "рендер на сервере", это полноценная архитектурная парадигма для SEO-оптимизации.
Генерация статических параметров
Для SSG (Static Site Generation) нужно сказать Next.js, какие варианты параметров существуют. Это делается через generateStaticParams:
// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
const posts = await fetch('https://api.example.com/posts').then(res => res.json())
return posts.map((post: any) => ({
slug: post.slug,
}))
}
async function getPost(slug: string) {
const res = await fetch(`https://api.example.com/posts/${slug}`)
return res.json()
}
export default async function BlogPost({ params }: { params: { slug: string } }) {
const post = await getPost(params.slug)
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
)
}
Эта функция запускается во время сборки. Next.js перебирает все варианты параметров и генерирует статические HTML-файлы. Это значит, что все ваши страницы будут доступны поисковикам без выполнения JavaScript.
Метаданные для SEO
Для управления мета-тегами используйте generateMetadata. Это функция, которая возвращает объект с метаданными страницы:
// app/blog/[slug]/page.tsx
import type { Metadata } from 'next'
export async function generateMetadata({ params }: { params: { slug: string } }): Promise<Metadata> {
const post = await getPost(params.slug)
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
type: 'article',
publishedTime: post.date,
authors: [post.author],
},
}
}
Эта функция тоже выполняется на сервере во время сборки. Результат — корректные <title>, <meta> теги и OpenGraph разметка, которые видны поисковикам.
Навигация между страницами
Для навигации используйте next/link. Это клиентский компонент, который prefetches данные для быстрого перехода:
import Link from 'next/link'
export default function BlogList() {
return (
<div>
<Link href="/blog/first-post">
First Post
</Link>
<Link href="/blog/second-post">
Second Post
</Link>
</div>
)
}
next/linkавтоматически prefetches данные для hovered ссылок, поэтому переходы мгновенные — это лучшее из обоих миров: серверный рендер для SEO и клиентская навигация для UX.
Pro Tips: Как получить максимум
- Используйте
generateStaticParamsдля статического контента — это даст вам предсказуемую производительность и мгновенную загрузку для всех известных путей. - Комбинируйте SSG и ISR — для статических страниц используйте
generateStaticParams, для динамического контента с редкими обновлениями добавьтеrevalidate: 60к fetch-запросам. - Не забывайте про
generateMetadata— это единственный способ добавить SEO-оптимизированные мета-теги для динамических страниц. - Разделяйте серверные и клиентские компоненты — всё, что можно рендерить на сервере, рендерите там. Клиентские компоненты только для интерактивности.
Common Pitfalls: Где люди ошибаются
- Путаница между
useRouterи файловой системой — не пытайтесь программно управлять маршрутами через хуки. URL должен определяться структурой директорий, а не клиентским кодом. - Отсутствие
generateStaticParams— без этой функции Next.js не знает, какие варианты параметров существуют, и не может генерировать статические страницы для каждого. - Игнорирование типизации
params— TypeScript помогает избежать ошибок, но только если вы правильно типизируете параметры маршрута. - Использование
useEffectдля загрузки данных — это антипаттерн для Next.js. Загружайте данные напрямую в компоненте сasync/await.
Переход к App Router — это не просто смена фреймворка, это смена мышления. URL перестаёт быть переменной состояния и становится частью архитектуры приложения. И именно это даёт вам SEO-оптимизацию без лишних костылей.