← Назад к вопросам
Как решишь задачу отдавать SEO-ботам сгенерированную статику а пользователям всю страницу?
3.0 Senior🔥 31 комментариев
#Архитектура и паттерны#Оптимизация и производительность
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
SEO: динамический контент для ботов vs полная страница для пользователей
Это критическая задача для современных веб-приложений: отдавать оптимизированный статический контент поисковым ботам и полнофункциональный интерактивный контент пользователям.
1. Server-Side Rendering (SSR) с Next.js
Самый современный и рекомендуемый подход.
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
// Output для standalone сборки на Dokku
output: 'standalone',
// Включаем SSR/ISR
experimental: {
isrMemoryCacheSize: 50 * 1024 * 1024, // 50MB
},
};
// app/layout.tsx
import { Metadata } from 'next';
export const metadata: Metadata = {
title: 'Мой сайт',
description: 'Описание для SEO',
openGraph: {
title: 'Мой сайт',
description: 'Описание для SEO',
url: 'https://example.com',
type: 'website',
images: [
{
url: 'https://example.com/og-image.jpg',
width: 1200,
height: 630,
},
],
},
};
// app/page.tsx
import { Suspense } from 'react';
import { InteractiveContent } from '@/components/interactive';
export default function Home() {
return (
<main>
<h1>Заголовок для SEO</h1>
<p>Статический контент для поисковых ботов</p>
{/* Интерактивный контент загружается после гидрации */}
<Suspense fallback={<div>Loading...</div>}>
<InteractiveContent />
</Suspense>
</main>
);
}
2. Обнаружение User-Agent
Определяем, бот это или реальный пользователь, и отдаём разный контент.
// lib/bot-detector.ts
export function isBot(userAgent: string): boolean {
const botPatterns = [
/googlebot/i,
/bingbot/i,
/slurp/i,
/duckduckgo/i,
/baiduspider/i,
/yandexbot/i,
/facebookexternalhit/i,
/twitterbot/i,
/linkedinbot/i,
/whatsapp/i,
/slack/i,
/telegram/i,
];
return botPatterns.some(pattern => pattern.test(userAgent));
}
// middleware.ts (Next.js)
import { NextRequest, NextResponse } from 'next/server';
import { isBot } from '@/lib/bot-detector';
export function middleware(request: NextRequest) {
const userAgent = request.headers.get('user-agent') || '';
const botDetected = isBot(userAgent);
// Передаём информацию о боте в заголовке
const requestHeaders = new Headers(request.headers);
requestHeaders.set('x-is-bot', botDetected.toString());
return NextResponse.next({
request: {
headers: requestHeaders,
},
});
}
3. Dynamic Content Rendering
Отдаём разный контент в зависимости от UA.
// components/content-dispatcher.tsx
import { headers } from 'next/headers';
export async function ContentDispatcher() {
const headersList = await headers();
const isBot = headersList.get('x-is-bot') === 'true';
if (isBot) {
return <SEOOptimizedContent />;
}
return <FullInteractiveContent />;
}
// Для SEO ботов
function SEOOptimizedContent() {
return (
<article>
<h1>Полный заголовок статьи</h1>
<p>Дружественный для SEO контент с ключевыми словами</p>
<section>
<h2>Раздел 1</h2>
<p>Подробное описание...</p>
</section>
<section>
<h2>Раздел 2</h2>
<p>Дополнительная информация...</p>
</section>
<nav className="breadcrumb">
<a href="/">Home</a>
<a href="/articles">Articles</a>
<span>Current Article</span>
</nav>
</article>
);
}
// Для реальных пользователей
function FullInteractiveContent() {
return (
<article>
<h1>Полный заголовок статьи</h1>
<aside>
<TableOfContents />
</aside>
<section>
<h2>Раздел 1</h2>
<InteractiveSection />
</section>
<section>
<h2>Раздел 2</h2>
<Comments />
</section>
<aside>
<RelatedArticles />
<Newsletter />
</aside>
</article>
);
}
4. Static Generation с ISR
Генерируем статику на запрос и кэшируем.
// app/articles/[slug]/page.tsx
import { notFound } from 'next/navigation';
export async function generateStaticParams() {
// Генерируем популярные статьи при сборке
const articles = await fetch('https://api.example.com/articles/popular')
.then(r => r.json());
return articles.map((article: any) => ({
slug: article.slug,
}));
}
export async function generateMetadata({ params }: { params: { slug: string } }) {
const article = await getArticle(params.slug);
return {
title: article.title,
description: article.description,
openGraph: {
title: article.title,
description: article.description,
type: 'article',
url: `https://example.com/articles/${article.slug}`,
images: [{ url: article.image }],
publishedTime: article.published,
authors: [article.author],
},
};
}
// ISR - Incremental Static Regeneration
export const revalidate = 3600; // Переге регулярно каждый час
export default async function ArticlePage({ params }: { params: { slug: string } }) {
const article = await getArticle(params.slug);
if (!article) notFound();
return (
<article>
<h1>{article.title}</h1>
<time>{article.published}</time>
<p className="lead">{article.description}</p>
{/* Статический контент для SEO */}
<div dangerouslySetInnerHTML={{ __html: article.html }} />
{/* Динамический контент */}
<aside>
<RelatedArticles slug={params.slug} />
<Comments slug={params.slug} />
</aside>
</article>
);
}
async function getArticle(slug: string) {
const res = await fetch(`https://api.example.com/articles/${slug}`, {
next: { revalidate: 3600 },
});
if (!res.ok) return null;
return res.json();
}
5. JSON-LD для структурированных данных
Помогаем ботам понимать контент структурированные данные.
// components/structured-data.tsx
export function ArticleStructuredData({
title,
description,
image,
author,
published,
modified,
content,
}: ArticleProps) {
const jsonld = {
'@context': 'https://schema.org',
'@type': 'Article',
headline: title,
description,
image,
author: {
'@type': 'Person',
name: author,
},
datePublished: published,
dateModified: modified,
articleBody: content,
};
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonld) }}
/>
);
}
// Использование
export default function Article() {
return (
<>
<ArticleStructuredData
title="Моя статья"
description="Описание"
image="https://example.com/image.jpg"
author="Иван Петров"
published="2024-01-01"
modified="2024-01-15"
content="Содержание статьи..."
/>
<article>
{/* ... контент ... */}
</article>
</>
);
}
6. Robots.txt и Sitemap
Управляем доступом ботов.
// public/robots.txt
User-agent: *
Allow: /
Disallow: /admin
Disallow: /private
Disallow: /*.json$
Sitemap: https://example.com/sitemap.xml
User-agent: AdsBot-Google
Allow: /
// app/sitemap.ts (Next.js 13+)
import { MetadataRoute } from 'next';
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const articles = await fetch('https://api.example.com/articles')
.then(r => r.json());
return [
{
url: 'https://example.com',
lastModified: new Date(),
changeFrequency: 'daily',
priority: 1,
},
...articles.map((article: any) => ({
url: `https://example.com/articles/${article.slug}`,
lastModified: new Date(article.modified),
changeFrequency: 'weekly',
priority: 0.8,
})),
];
}
7. Полная реализация с кэшированием
// app/page.tsx
import { cache } from 'react';
import { isBot } from '@/lib/bot-detector';
// Кэшируем результат fetch на время рендера
const getCachedData = cache(async () => {
return fetch('https://api.example.com/data', {
next: { revalidate: 300 }, // 5 минут
}).then(r => r.json());
});
export const revalidate = 300;
export async function generateMetadata() {
const data = await getCachedData();
return {
title: data.title,
description: data.description,
openGraph: {
images: [{ url: data.image }],
},
};
}
export default async function Home() {
const data = await getCachedData();
const userAgent = (await import('next/headers')).headers().get('user-agent') || '';
const bot = isBot(userAgent);
return (
<main>
{/* Всегда показываем SEO-оптимальный контент */}
<h1>{data.title}</h1>
<p>{data.description}</p>
<img src={data.image} alt={data.title} />
{/* Интерактивный контент только для реальных пользователей */}
{!bot && (
<>
<Comments articleId={data.id} />
<InteractiveFeatures />
<Newsletter />
</>
)}
</main>
);
}
8. Prerendering для критических страниц
// scripts/prerender.js
const fs = require('fs');
const path = require('path');
async function prerender() {
const routes = [
'/',
'/about',
'/contact',
'/articles',
];
for (const route of routes) {
const html = await renderToString(route);
const filepath = path.join(process.cwd(), '.next', 'prerendered', route + '.html');
fs.mkdirSync(path.dirname(filepath), { recursive: true });
fs.writeFileSync(filepath, html);
console.log(`Prerendered: ${route}`);
}
}
prerender();
Best Practices для SEO
- Используй Next.js с SSR/ISR - лучший выбор для SEO
- Генерируй metadata динамически - актуальные OpenGraph теги
- Добавляй JSON-LD - структурированные данные для ботов
- Оптимизируй images - используй next/image с alt текстами
- Кэшируй агрессивно - ISR для часто запрашиваемых страниц
- Проверяй мобильность - Page Speed Insights очень важен для ранкинга
- Тестируй с Google Search Console - видишь проблемы рано
- Используй robots.txt и sitemap - помогает ботам ориентироваться
- Избегай JavaScript-only контента - боты не всегда выполняют JS
- Мониторь Core Web Vitals - LCP, FID, CLS критичны для SEO
Лучшая стратегия: SSR/ISR с Next.js + User-Agent detection + JSON-LD. Это гарантирует что боты получат оптимизированный контент, а пользователи полнофункциональное приложение.