SEO-архитектура портала на Next.js строится на трех вещах: продуманных маршрутах App Router, файлах sitemap.ts и robots.ts, и JSON-LD разметке schema.org для каждого типа страницы. Без этой связки Google и Яндекс просто не находят половину каталога,…
400 000+ органических переходов за 3 месяца. Со-основатель GoBanana (231K пользователей, 12+ млн ₽ без рекламы) и NeuroScribe (65K пользователей). SEO/GEO-стратегии для AI-поисковиков, 1 700+ единиц контента, 17+ реализованных стратегий.
Об авторе →Claude Code: новый CLI-агент от Anthropic
Anthropic выпустила Claude Code — терминальный AI-агент для разработчиков. Инструмент работает прямо в командной строке и умеет писать, редактировать и запускать код.
Zcode AI: Полный гид по визуальному интерфейсу для Claude Code и AI-агентов
Узнайте, как использовать Zcode для управления Claude Code, Gemini и Codex в едином GUI. Настройка провайдеров, MCP-серверов и визуальный вайбкодинг.
YouTube-канал с монетизацией из любой точки мира: Пошаговый гайд 2026
Инструкция по созданию YouTube-канала: обход блокировок SMS, настройка расширенных функций через виртуальные номера и правила безопасности для монетизации.
Windsurf Code Maps: Как глубоко понимать архитектуру проекта перед написанием кода
Полный гайд по Windsurf Code Maps, модели Sway 1.5 и Sway Grep. Узнайте, как визуализировать архитектуру кода и ускорить разработку в 13 раз.
Vk Fast Cash Strategy
Аудитория ВКонтакте — это те же люди, что и в Instagram, но 'социальный контракт' площадки другой. Если Instagram — это 'дорогой ресторан' с демонстрацией успеха, то VK — это 'душевная шашлычная'. Здесь не работает глянцевый 'успешный успех
SEO-архитектура портала на Next.js строится на трех вещах: продуманных маршрутах App Router, файлах sitemap.ts и robots.ts, и JSON-LD разметке schema.org для каждого типа страницы. Без этой связки Google и Яндекс просто не находят половину каталога, а нейровыдача вроде Google AI Overview вообще не подхватывает страницу. Ниже разберем архитектуру на примере каталога и блога: как устроены маршруты, откуда брать данные для sitemap и какую schema ставить под инструмент, статью и FAQ.
В статье: структура маршрутов для каталога и блога, готовый код динамического sitemap с sitemap index для больших каталогов, robots.ts с закрытыми путями и три JSON-LD схемы под разные типы контента. Все примеры на Next.js 14 App Router, проверены на проекте с 6 200 материалами.

Каждый тип контента получает свой сегмент: /blog/[slug] для статей, /item/[slug] для инструментов, /category/[slug] для категорий. Разделение по сегментам дает независимую генерацию, кеширование и метаданные для каждого типа.
На портале с каталогом и блогом обычно четыре базовых маршрута: app/blog/[slug]/page.tsx для статей, app/item/[slug]/page.tsx для карточек инструментов, app/category/[slug]/page.tsx для листингов и app/agents/page.tsx для отдельного каталога. У каждого сегмента своя функция generateStaticParams, которая на этапе сборки заранее подтягивает список slug из базы или CMS.
Вот в чем разница с Pages Router. Там маршруты собирались вручную через getStaticPaths, и SEO-логика размазывалась по _document.tsx и next/head. В App Router метаданные живут прямо в файле страницы через экспорт generateMetadata, а статика генерируется автоматически при билде.
// app/item/[slug]/page.tsx
export async function generateStaticParams() {
const tools = await getAllTools(); // slug, updatedAt
return tools.map((tool) => ({ slug: tool.slug }));
}
export const revalidate = 86400; // ISR раз в сутки
export async function generateMetadata({ params }) {
const tool = await getToolBySlug(params.slug);
return {
title: `${tool.name}: обзор, цены и альтернативы 2026`,
description: tool.shortDescription,
alternates: { canonical: `https://vibecoderz.ru/item/${tool.slug}` },
};
}Для каталога с сотнями инструментов и тысячами статей полная статика на билде становится долгой. Здесь помогает ISR: revalidate = 86400 держит страницу свежей раз в сутки без пересборки всего проекта. Для новостей и часто обновляемых карточек ставят revalidate = 3600, раз в час.

generateStaticParams заранее генерирует список маршрутов на этапе сборки, поэтому краулер получает готовый HTML вместо ожидания рендера на лету. Это сокращает время до первого байта и повышает шанс попасть в индекс с первого захода бота.
Роботы Google и Яндекса не любят ждать. Если страница рендерится динамически при каждом запросе, краулер получает медленный ответ и может пометить URL как низкоприоритетный. generateStaticParams решает это на корню: все известные slug превращаются в статические HTML-файлы еще на этапе next build.
Для портала с каталогом инструментов и блогом это выглядит так: функция обходит все таблицы item и blogPost, забирает slug и updatedAt, и передает их в билд. Новые страницы, которых не было на момент сборки, обрабатываются через dynamicParams: true по умолчанию, то есть Next.js сам дорендерит их при первом заходе и закеширует.
Практический нюанс: если в каталоге 5000+ карточек, полный билд может занять 10-15 минут. В таком случае часть каталога стоит оставить на чистый ISR без предгенерации, а в статику включать только топ по трафику.

Файл app/sitemap.ts возвращает MetadataRoute.Sitemap с массивом URL, приоритетов и дат обновления. Для каталога с несколькими типами контента данные собираются из всех источников в один массив перед возвратом.
Для небольшого и среднего портала одного файла app/sitemap.ts достаточно. Он собирает статические страницы, все статьи блога и все карточки каталога в единый список.
// app/sitemap.ts
import { MetadataRoute } from 'next';
import { getAllTools, getAllPosts, getAllCategories } from '@/lib/data';
const BASE_URL = 'https://vibecoderz.ru';
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const [tools, posts, categories] = await Promise.all([
getAllTools(),
getAllPosts(),
getAllCategories(),
]);
const staticRoutes = [
{ url: BASE_URL, lastModified: new Date(), priority: 1.0, changeFrequency: 'daily' as const },
{ url: `${BASE_URL}/ide`, lastModified: new Date(), priority: 0.9, changeFrequency: 'weekly' as const },
{ url: `${BASE_URL}/agents`, lastModified: new Date(), priority: 0.8, changeFrequency: 'weekly' as const },
];
const toolRoutes = tools.map((tool) => ({
url: `${BASE_URL}/item/${tool.slug}`,
lastModified: tool.updatedAt,
priority: 0.9,
changeFrequency: 'weekly' as const,
}));
const postRoutes = posts.map((post) => ({
url: `${BASE_URL}/blog/${post.slug}`,
lastModified: post.lastUpdated,
priority: 0.8,
changeFrequency: 'weekly' as const,
}));
const categoryRoutes = categories.map((cat) => ({
url: `${BASE_URL}/category/${cat.slug}`,
lastModified: new Date(),
priority: 0.7,
changeFrequency: 'monthly' as const,
}));
return [...staticRoutes, ...toolRoutes, ...postRoutes, ...categoryRoutes];
}Важная деталь по lastModified: сюда идет реальная дата обновления записи из базы, а не текущая дата на каждый билд. Иначе Google видит, что все страницы "обновились" одновременно, и перестает доверять этому полю как сигналу свежести.
Один файл sitemap.xml ограничен 50 000 URL и 50 МБ несжатого текста. Если каталог превышает лимит, Next.js разбивает его на несколько файлов через generateSitemaps и автоматически собирает sitemap index.

Пока в каталоге меньше 40-45 тысяч URL, обычный app/sitemap.ts работает без проблем. Но у порталов с товарным каталогом или тясячами материалов лимит подступает быстро. Здесь на помощь приходит generateSitemaps: функция возвращает массив идентификаторов чанков, а сама функция sitemap() получает id и отдает только свой кусок URL.
// app/item/sitemap.ts
import { MetadataRoute } from 'next';
import { db } from '@/lib/db';
const ITEMS_PER_SITEMAP = 50000;
export async function generateSitemaps() {
const total = await db.item.count();
const count = Math.ceil(total / ITEMS_PER_SITEMAP);
return Array.from({ length: count }, (_, id) => ({ id }));
}
export default async function sitemap({ id }: { id: number }): Promise<MetadataRoute.Sitemap> {
const items = await db.item.findMany({
skip: id * ITEMS_PER_SITEMAP,
take: ITEMS_PER_SITEMAP,
});
return items.map((item) => ({
url: `https://vibecoderz.ru/item/${item.slug}`,
lastModified: item.updatedAt,
}));
}Next.js сам генерирует файлы вида /item/sitemap/0.xml, /item/sitemap/1.xml и собирает индекс. По документации Next.js, этот механизм появился именно для разбиения крупных каталогов без ручной сборки XML. Google при этом поддерживает до 500 файлов в одном sitemap index, каждый до 50 000 записей, так что запаса хватает даже на очень крупный проект.
Максим: «Портал собрали за неделю тремя скриптами голосом в Claude Code, и на 6 200 материалах обычный sitemap.ts еще спокойно тянул все URL одним файлом. Sitemap index закладывали заранее, на рост. Дешевле сделать сразу с запасом, чем потом переписывать под нагрузкой.»
Файл app/robots.ts управляет доступом краулеров к разделам сайта и указывает путь к sitemap. Закрывать нужно только служебные разделы: админку, API, черновики и личный кабинет.
Если sitemap.ts говорит роботам, куда идти, то robots.ts говорит, куда не идти. Для портала с каталогом и личным кабинетом типичный набор закрытых путей: /api, /dashboard, /studio (Sanity Studio), /auth и страницы с параметрами фильтров, которые дублируют контент.
// app/robots.ts
import { MetadataRoute } from 'next';
export default function robots(): MetadataRoute.Robots {
return {
rules: {
userAgent: '*',
allow: '/',
disallow: ['/api/', '/dashboard/', '/studio/', '/auth/', '/*?*sort='],
},
sitemap: 'https://vibecoderz.ru/sitemap.xml',
};
}Отдельно про AI-краулеров. GPTBot, ClaudeBot и PerplexityBot в 2026 году читают тот же robots.txt и тот же sitemap, что и Googlebot, чтобы решить, какие страницы цитировать в ответах. Если закрыть от них важные разделы каталога, страница физически не сможет попасть в AI Overview или в ответ Perplexity, даже если весь остальной SEO сделан идеально.

Для инструмента в каталоге используется схема SoftwareApplication, для статьи блога BlogPosting, а для блока вопросов FAQPage. Без JSON-LD Google AI Overview обычно не подхватывает страницу, даже если контент по сути хороший.
На каждый тип контента своя схема, и смешивать их в одном компоненте не стоит: страница инструмента получает SoftwareApplication, страница блога BlogPosting, а FAQ-блок внутри любой страницы дополнительно оборачивается в FAQPage.
// components/schema/tool-schema.tsx
export function ToolSchema({ tool }) {
const schema = {
'@context': 'https://schema.org',
'@type': 'SoftwareApplication',
name: tool.name,
description: tool.shortDescription,
applicationCategory: 'DeveloperApplication',
operatingSystem: 'Web, macOS, Windows',
offers: {
'@type': 'Offer',
price: tool.priceFrom,
priceCurrency: 'USD',
},
url: `https://vibecoderz.ru/item/${tool.slug}`,
};
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
/>
);
}Для статей блога структура другая: BlogPosting с датами публикации и обновления, плюс отдельный блок FAQPage, если в статье есть раздел вопросов.
Компонент со схемой удобно вставлять прямо в generateMetadata рядом с обычными title и description, чтобы не забывать про него на новых страницах. Один пропущенный тип контента без schema, и именно эти страницы выпадают из AI-выдачи.

| Тип маршрута | Способ генерации | Когда использовать | Revalidate |
|---|---|---|---|
| Статический (/pricing, /faq) | Полная статика на билде | Страницы, которые почти не меняются | Без ISR |
| ISR через generateStaticParams | Статика + фоновое обновление | Карточки инструментов, статьи блога | 3600-86400 сек |
| Полностью динамический (route handler) | Рендер на каждый запрос | Личный кабинет, страницы с авторизацией | Не применяется |
Ошибка, которую чаще всего допускают на растущих порталах: весь каталог тянут через полную статику без ISR. При 200-300 карточках это незаметно, но при 3000+ билд начинает занимать 20+ минут, и каждое обновление одной карточки требует пересборки всего проекта. ISR с revalidate снимает эту проблему полностью.

Перед запуском портала на Next.js стоит пройтись по короткому списку: маршруты разбиты по сегментам с собственным generateMetadata, sitemap.ts собирает все типы контента с реальными датами обновления, для каталога свыше 40 тысяч URL подключен generateSitemaps, robots.ts закрывает только служебные разделы и указывает путь к sitemap, а JSON-LD стоит на каждом типе страницы: SoftwareApplication для инструментов, BlogPosting для статей, FAQPage для блоков вопросов.
Готовые обзоры инструментов для вайбкодинга, включая Cursor и Windsurf, в каталоге vibecoderz.ru/ide собраны как раз по этой архитектуре: у каждой карточки своя schema, ISR-кеш и место в sitemap index.

app.Сколько URL помещается в один sitemap.xml в Next.js?
Лимит из спецификации sitemaps.org - 50 000 URL или 50 МБ несжатого файла. Если каталог больше, используйте generateSitemaps для разбиения на несколько файлов.
Нужен ли отдельный sitemap для каждого раздела портала?
Не обязательно. Один app/sitemap.ts в корне справляется, пока общее число URL не приближается к лимиту. Отдельные файлы по сегментам (app/item/sitemap.ts) имеет смысл делать, когда конкретный раздел растет быстрее остальных.
Что будет, если не поставить JSON-LD схему на страницу?
Страница все еще может индексироваться в обычном поиске, но не попадет в Google AI Overview и хуже цитируется в Perplexity и ChatGPT, потому что у них нет структурированного сигнала о типе контента.
Как часто обновлять revalidate для каталога инструментов?
Для карточек инструментов, где цены и статусы меняются нечасто, достаточно revalidate = 86400 (раз в сутки). Для новостного раздела ставят revalidate = 3600.
Можно ли закрыть от индексации страницы фильтров каталога?
Да, и стоит. Страницы вида /item?sort=price дублируют основной листинг и размывают crawl budget. Их закрывают в robots.ts через паттерн disallow.
Учитывают ли AI-краулеры robots.txt так же, как Googlebot?
Да. GPTBot, ClaudeBot и PerplexityBot в 2026 году читают тот же robots.txt и тот же sitemap, что и обычные поисковые боты, чтобы решить, какие страницы использовать для ответов.
Что делать, если билд с полной статикой стал слишком долгим?
Переводить основную часть каталога на ISR вместо предгенерации через generateStaticParams, оставляя предгенерацию только для топовых по трафику страниц.
Готовые схемы для вставки в generateMetadata этой страницы: BlogPosting описывает саму статью, FAQPage подтягивает блок вопросов выше для Google AI Overview и Perplexity.
Если нужна помощь с архитектурой конкретного портала, запишитесь на консультацию к Максиму.
Обновлено: июль 2026.