Пошук уроків, статей та іншого контенту
Оптимізуєте SEO статей і навчальних сторінок за допомогою метаданих, розмітки Article та коректної структури контенту.
SEO допомагає пошуковим системам зрозуміти:
про що сторінка;
чи є вона статтею, навчальним матеріалом або новиною;
який заголовок і опис показувати в результатах пошуку;
хто автор і коли матеріал було опубліковано;
як частини сторінки пов’язані між собою.
Для статей у Next.js потрібно працювати на трьох рівнях:
Метадані сторінки.
Структурована розмітка Article.
Семантична структура самого контенту.
Жоден із цих підходів не гарантує вищу позицію в пошуку. Вони лише допомагають пошуковим системам коректніше інтерпретувати якісний контент.
У Next.js з App Router метадані можна описати через:
статичний об’єкт metadata;
функцію generateMetadata для динамічних сторінок.
Для сторінки зі статтею метадані зазвичай містять:
title;
description;
alternates.canonical;
openGraph;
twitter;
robots, якщо потрібно змінити правила індексації.
Якщо всі сторінки мають однакові або наперед відомі метадані, використовуйте об’єкт metadata:
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: 'Як працює кешування в Next.js',
description:
'Пояснення принципів кешування в Next.js та приклади використання на практиці.',
alternates: {
canonical: 'https://example.com/articles/nextjs-caching',
},
};title має коротко описувати сторінку. Не варто додавати до нього довгий список ключових слів.
description має пояснювати зміст сторінки природною мовою. Вона може використовуватися пошуковою системою як опис результату пошуку, але пошуковик не зобов’язаний показувати саме це значення.
Для сторінок із динамічним маршрутом, наприклад /articles/[slug], метадані потрібно генерувати на основі конкретної статті.
Приклад структури даних:
type Article = {
slug: string;
title: string;
excerpt: string;
content: string[];
author: string;
publishedAt: string;
updatedAt?: string;
coverUrl?: string;
};Функція generateMetadata може отримати статтю та сформувати для неї унікальні метадані:
import type { Metadata } from 'next';
import { notFound } from 'next/navigation';
type Article = {
slug: string;
title: string;
excerpt: string;
content: string[];
author: string;
publishedAt: string;
updatedAt?: string;
coverUrl?: string;
};
const articles: Article[] = [
{
slug: 'nextjs-caching',
title: 'Як працює кешування в Next.js',
excerpt:
'Пояснення принципів кешування в Next.js та приклади використання на практиці.',
content: [
'Кешування допомагає зменшити кількість повторних обчислень і запитів.',
'У Next.js стратегію кешування потрібно обирати відповідно до типу даних.',
],
author: 'Олена Коваль',
publishedAt: '2026-01-15',
updatedAt: '2026-02-01',
coverUrl: 'https://example.com/images/nextjs-caching.jpg',
},
];
function getArticle(slug: string) {
return articles.find((article) => article.slug === slug);
}
type PageProps = {
params: Promise<{ slug: string }>;
};
export async function generateMetadata({
params,
}: PageProps): Promise<Metadata> {
const { slug } = await params;
const article = getArticle(slug);
if (!article) {
return {
title: 'Статтю не знайдено',
robots: {
index: false,
follow: false,
},
};
}
const url = `https://example.com/articles/${article.slug}`;
return {
title: article.title,
description: article.excerpt,
alternates: {
canonical: url,
},
openGraph: {
type: 'article',
url,
title: article.title,
description: article.excerpt,
publishedTime: article.publishedAt,
modifiedTime: article.updatedAt,
authors: [article.author],
images: article.coverUrl
? [
{
url: article.coverUrl,
alt: article.title,
},
]
: undefined,
},
twitter: {
card: 'summary_large_image',
title: article.title,
description: article.excerpt,
images: article.coverUrl ? [article.coverUrl] : undefined,
},
};
}
export default async function ArticlePage({ params }: PageProps) {
const { slug } = await params;
const article = getArticle(slug);
if (!article) {
notFound();
}
const url = `https://example.com/articles/${article.slug}`;
const structuredData = {
'@context': 'https://schema.org',
'@type': 'Article',
headline: article.title,
description: article.excerpt,
url,
datePublished: article.publishedAt,
...(article.updatedAt && { dateModified: article.updatedAt }),
author: {
'@type': 'Person',
name: article.author,
},
...(article.coverUrl && { image: [article.coverUrl] }),
};
// Захищаємо JSON-LD від передчасного завершення тега script.
const jsonLd = JSON.stringify(structuredData).replace(/</g, '\\u003c');
return (
<main>
<article>
<header>
<p>Стаття</p>
<h1>{article.title}</h1>
<p>{article.excerpt}</p>
<p>
Автор: <span>{article.author}</span>
</p>
<time dateTime={article.publishedAt}>
Опубліковано: {article.publishedAt}
</time>
{article.updatedAt && (
<time dateTime={article.updatedAt}>
Оновлено: {article.updatedAt}
</time>
)}
</header>
<div>
{article.content.map((paragraph) => (
<p key={paragraph}>{paragraph}</p>
))}
</div>
</article>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: jsonLd }}
/>
</main>
);
}У сучасних версіях Next.js params в асинхронних серверних компонентах має тип Promise. Якщо проєкт використовує старішу версію Next.js, параметри маршруту можуть бути звичайним об’єктом.
title.template для спільного брендуСпільну частину заголовка зручно налаштувати в кореневому layout.tsx:
import type { Metadata } from 'next';
export const metadata: Metadata = {
metadataBase: new URL('https://example.com'),
title: {
default: 'Fullstack Academy',
template: '%s | Fullstack Academy',
},
description: 'Навчальні матеріали для розробників.',
};Тоді сторінка зі значенням:
export const metadata: Metadata = {
title: 'Як працює кешування в Next.js',
};матиме заголовок:
Як працює кешування в Next.js | Fullstack AcademyКанонічна адреса повідомляє пошуковій системі, яка URL є основною для конкретного матеріалу.
Це важливо, якщо одна стаття доступна через кілька адрес, наприклад:
/articles/nextjs-caching
/articles/nextjs-caching?source=mailДля основної сторінки вкажіть:
export const metadata: Metadata = {
alternates: {
canonical: 'https://example.com/articles/nextjs-caching',
},
};Канонічна адреса повинна:
бути абсолютною;
вказувати на доступну сторінку;
відповідати основній версії матеріалу;
не містити зайвих tracking-параметрів.
Canonical не замінює перенаправлення. Якщо URL більше не має існувати, краще використовувати відповідний HTTP-статус або redirect.
ArticleМетадані описують сторінку в її <head>, а структурована розмітка описує сутність сторінки для пошукових систем.
Для статті використовується schema.org-тип Article. Його зазвичай додають як JSON-LD:
const structuredData = {
'@context': 'https://schema.org',
'@type': 'Article',
headline: 'Як працює кешування в Next.js',
description: 'Пояснення принципів кешування в Next.js.',
url: 'https://example.com/articles/nextjs-caching',
datePublished: '2026-01-15',
dateModified: '2026-02-01',
author: {
'@type': 'Person',
name: 'Олена Коваль',
},
image: ['https://example.com/images/nextjs-caching.jpg'],
};Основні властивості:
@context визначає словник schema.org;
@type визначає тип сутності;
headline містить заголовок статті;
description містить короткий опис;
url містить канонічну адресу;
datePublished містить дату публікації;
dateModified містить дату останньої зміни;
author описує автора;
image містить зображення статті.
Значення в JSON-LD повинні відповідати видимому контенту сторінки. Наприклад, не слід вказувати в dateModified дату, якої немає на сторінці або в даних CMS.
У React-компоненті JSON-LD додається через тег script:
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(structuredData).replace(/</g, '\\u003c'),
}}
/>dangerouslySetInnerHTML потрібен, оскільки JSON-LD має бути текстом усередині script. Дані повинні формуватися з довіреного джерела та коректно серіалізуватися.
Заміна символу < на \u003c зменшує ризик того, що значення з контенту передчасно завершить тег script.
Article, NewsArticle і BlogPostingДля звичайного навчального матеріалу найчастіше достатньо:
{
"@type": "Article"
}Тип NewsArticle призначений для новинного контенту, а BlogPosting є спеціалізованим типом для публікацій у блозі.
Не потрібно обирати точніший тип лише заради SEO. Він має відповідати фактичному характеру матеріалу.
Пошукові системи аналізують не лише метадані, а й сам HTML-документ. Семантична структура робить матеріал зрозумілішим і для пошукових систем, і для користувачів.
h1На сторінці статті має бути один основний заголовок:
<h1>Як працює кешування в Next.js</h1>Не варто використовувати h1 для логотипа, назви сайту та заголовка статті одночасно.
Ієрархія заголовків повинна бути послідовною:
<h1>Як працює кешування в Next.js</h1>
<h2>Що таке кешування</h2>
<h3>Кешування даних</h3>
<h2>Стратегії кешування</h2>
<h3>Статичні дані</h3>
<h3>Динамічні дані</h3>Заголовки описують структуру документа, а не лише візуальний розмір тексту.
articleОсновний матеріал варто розміщувати в <article>:
<main>
<article>
<header>
<h1>Як працює кешування в Next.js</h1>
<p>Короткий опис матеріалу.</p>
</header>
<section>
<h2>Що таке кешування</h2>
<p>Кешування зменшує кількість повторних операцій.</p>
</section>
</article>
</main>Рекомендоване призначення елементів:
<main> — основний унікальний вміст сторінки;
<article> — самостійний матеріал;
<header> — заголовок і вступні дані;
<section> — тематично окремий розділ;
<footer> — додаткові дані матеріалу, якщо вони є;
<time> — дата або час.
Вступний абзац повинен одразу пояснювати:
яку проблему вирішує матеріал;
для кого він призначений;
що читач зрозуміє після його опрацювання.
Не починайте статтю з великого набору ключових слів. Пошукова оптимізація не повинна погіршувати читабельність.
Приклад:
<article>
<header>
<h1>Валідація форм у Next.js</h1>
<p>
У цьому матеріалі розглянемо, як перевіряти дані форми на сервері
та повертати користувачу зрозумілі повідомлення про помилки.
</p>
</header>
<section>
<h2>Підготовка форми</h2>
<p>Спочатку визначимо поля та правила їх перевірки.</p>
</section>
<section>
<h2>Обробка помилок</h2>
<p>Після валідації можна показати помилки біля відповідних полів.</p>
</section>
</article>Навчальна сторінка може мати схожу структуру зі статтею, але зазвичай містить додаткові елементи:
рівень складності;
цілі навчання;
попередні вимоги;
послідовні розділи;
практичні завдання;
підсумок.
Заголовок сторінки має описувати конкретну тему:
Метадані в Next.js App RouterЗамість загального:
Урок 5Назву уроку або номер можна залишити додатковою інформацією, але не робити єдиним основним заголовком.
Приклад структури:
<main>
<article>
<header>
<p>Рівень: Intermediate</p>
<h1>SEO для статей у Next.js</h1>
<p>
Навчіться додавати метадані, JSON-LD та семантичну структуру
до навчальних сторінок.
</p>
</header>
<section>
<h2>Цілі уроку</h2>
<ul>
<li>Додавати динамічні метадані.</li>
<li>Описувати статтю через JSON-LD.</li>
<li>Будувати правильну ієрархію заголовків.</li>
</ul>
</section>
<section>
<h2>Метадані сторінки</h2>
<p>Метадані допомагають описати сторінку пошуковій системі.</p>
</section>
<section>
<h2>Підсумок</h2>
<p>Тепер сторінка має зрозумілу структуру та опис статті.</p>
</section>
</article>
</main>Кожна навчальна сторінка повинна мати власні:
title;
description;
canonical URL;
основний h1;
структуровані дані, якщо сторінка фактично є статтею.
Не копіюйте однаковий description для всіх уроків. Унікальний опис допомагає відрізняти сторінки між собою.
Для статті корисно відображати дату публікації:
<time dateTime="2026-01-15">
15 січня 2026 року
</time>А якщо матеріал суттєво оновлювався — і дату оновлення:
<p>
Опубліковано:{' '}
<time dateTime="2026-01-15">15 січня 2026 року</time>
</p>
<p>
Оновлено:{' '}
<time dateTime="2026-02-01">1 лютого 2026 року</time>
</p>Атрибут dateTime повинен містити машинно читабельне значення, зазвичай у форматі YYYY-MM-DD або повному ISO-форматі.
Не змінюйте дату оновлення автоматично під час кожного рендерингу. Вона має відображати реальну зміну матеріалу.
Якщо стаття не знайдена, не слід рендерити сторінку з порожнім або неправильним контентом.
У серверному компоненті використовуйте notFound():
import { notFound } from 'next/navigation';
export default async function Page({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const article = await getArticle(slug);
if (!article) {
notFound();
}
return <article>{article.title}</article>;
}Для сторінки, якої не існує, Next.js покаже спеціальний інтерфейс 404. У метаданих такої сторінки не потрібно рекламувати неіснуючий матеріал.
export const metadata: Metadata = {
title: 'Навчальні матеріали',
description: 'Усі наші навчальні матеріали.',
};Такий варіант може бути прийнятним для загальної сторінки каталогу, але не для кожної окремої статті.
Для динамічних матеріалів використовуйте generateMetadata.
h1Заголовок на кшталт Урок 7 не пояснює тему сторінки. Використовуйте змістовний заголовок:
<h1>Генерація метаданих для динамічних сторінок у Next.js</h1>Не вказуйте в структурованих даних:
інший заголовок;
іншу дату;
автора, якого немає на сторінці;
зображення, якого не існує;
URL іншої сторінки.
Розмітка повинна описувати саме поточний видимий матеріал.
h1 і метаданих без потребиВізуальний заголовок сторінки та title можуть бути схожими, але вони виконують різні ролі:
h1 є частиною контенту сторінки;
title відображається у вкладці браузера та може використовуватися в результатах пошуку.
Не потрібно штучно робити їх різними або додавати до обох зайві ключові слова.
dangerouslySetInnerHTML до неекранованого текстуJSON-LD потрібно серіалізувати через JSON.stringify і коректно обробляти перед вставкою в script. Не вставляйте довільний текст у HTML без необхідності.
Надмірне повторення фраз на кшталт «Next.js SEO статті навчання» погіршує якість тексту. Використовуйте терміни там, де вони природно описують тему.
Перед публікацією статті перевірте:
чи має сторінка унікальний title;
чи описує description конкретний матеріал;
чи вказано правильний canonical URL;
чи є один змістовний h1;
чи використовуються h2 для основних розділів;
чи знаходиться матеріал усередині <article>;
чи відповідають datePublished, dateModified і author фактичним даним;
чи збігається JSON-LD із видимим контентом;
чи не генерується сторінка для неіснуючого slug;
чи працює сторінка з прямим переходом за її URL.
Для SEO статей і навчальних сторінок у Next.js:
генеруйте метадані для кожного матеріалу;
задавайте унікальні title і description;
використовуйте canonical URL;
додавайте Open Graph-дані для поширення сторінки;
описуйте статтю через JSON-LD з типом Article;
використовуйте один h1 і послідовну ієрархію h2 та h3;
структуруйте матеріал за допомогою <main>, <article>, <header>, <section> і <time>;
показуйте реальні дати та автора;
не використовуйте SEO-розмітку для даних, яких немає у видимому контенті.