Обязательно ли использовать canonical в generateMetadata? Разбираем на практике
Мета-тег canonical — это не просто «хорошая практика», а краеугольный камень SEO в современном вебе. Если вы работаете с Next.js и App Router, игнорирование этого тега — это прямое указание поисковым системам ранжировать ваш сайт хуже, чем он того заслуживает.
Что такое canonical и зачем он нужен?
Простыми словами: канонический URL — это главный, предпочтительный адрес страницы.
Представьте, что у вас есть продукт, который можно найти по разным путям:
https://store.com/products/awesome-widgethttps://store.com/category/gadgets/awesome-widgethttps://store.com/products/awesome-widget?utm_source=facebook
Для поисковых систем (Google, Yandex) это 3 разные страницы. Они могут начать ранжировать их отдельно, дробить вес (линковый и поведенческий) и, в худшем случае, посчитать это дублирующим контентом.
Канонический тег решает эту проблему. Он говорит поисковому роботу: «Эй, все эти адреса — одна и та же страница! Ранжируй вот этот, основной URL.»
<!-- Такой тег в <head> указывает на главную версию -->
<link rel="canonical" href="https://store.com/products/awesome-widget" />
```html
## Next.js App Router: Где и как это делать?
В старом Pages Router мы бы прописывали это в next/head. В современном App Router вся работа с метаданными сосредоточена в функции generateMetadata.
## Базовый пример: Статический canonical
Допустим, у нас страница app/products/page.tsx. Её канонический URL всегда один и тот же.
```tsx
// app/products/page.tsx
import { Metadata } from 'next';
export function generateMetadata(): Metadata {
return {
title: 'Все наши продукты',
description: 'Лучшие продукты на рынке.',
// Самое главное:
alternates: {
canonical: '/products'
},
};
}
export default function ProductsPage() {
// ... JSX вашей страницы
}
Что здесь происходит? Next.js автоматически превратит относительный путь /products в абсолютный URL (например, https://yourdomain.com/products) и добавит тег
<link rel="canonical" ... /> в <head> страницы.
Продвинутый пример: Динамический canonical (SSR/SSG)
На практике чаще всего canonical нужен для динамических страниц, например, страниц товаров или статей.
// app/blog/[slug]/page.tsx
import { Metadata, ResolvingMetadata } from 'next';
type Props = {
params: { slug: string };
searchParams: { [key: string]: string | string[] | undefined };
}
// Функция для получения данных (из вашего CMS или API)
async function getPost(slug: string) {
const res = await fetch(`https://api.example.com/posts/${slug}`);
return res.json();
}
export async function generateMetadata(
{ params, searchParams }: Props,
parent: ResolvingMetadata
): Promise<Metadata> {
// Получаем данные поста
const post = await getPost(params.slug);
// Формируем полный абсолютный URL для canonical
const canonicalUrl = `${process.env.NEXT_PUBLIC_SITE_URL}/blog/${post.slug}`;
return {
title: post.seo_title,
description: post.meta_description,
openGraph: {
title: post.seo_title,
description: post.meta_description,
url: canonicalUrl, // Важно и для OG!
// ... другие OG-теги
},
// Ключевой момент:
alternates: {
canonical: canonicalUrl,
},
};
}
export default function BlogPostPage({ params }: Props) {
// ... React-компонент страницы
}
Ключевые моменты этого кода:
- process.env.NEXT_PUBLIC_SITE_URL: Мы используем переменную окружения для базового URL. Это MUST-HAVE для продакшена. Задайте её в .env.local:
NEXT_PUBLIC_SITE_URL=https://your-production-domain.com
2. Синхронизация с openGraph:url: Мы используем тот же canonicalUrl для OpenGraph-тега url. Это обеспечивает согласованность, когда ваша страница делится в соцсетях.
- Чистота URL: Обратите внимание, мы используем "чистый" URL без query-параметров (?utm_source=...). Именно это и должна делать canonical — отсекать служебные параметры.
Частые кейсы и ошибки
1. Страницы пагинации
Для страниц пагинации canonical должен вести на первую страницу, если контент дублируется.
// app/blog/page/[num]/page.tsx
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const pageNum = parseInt(params.num);
return {
alternates: {
// Для первой страницы canonical ведет на себя, для остальных — на первую.
canonical: pageNum === 1 ? '/blog' : `/blog/page/${pageNum}`,
},
};
}
2. Страницы с фильтрами и сортировкой
Это сложный случай. Если при применении фильтров контент страницы меняется кардинально (например, показываются товары другого типа), то такая страница может считаться уникальной и иметь свой собственный canonical.
Но! Если изменения незначительны или это просто сортировка одних и тех же товаров, лучшей практикой является указание canonical на URL без параметров фильтрации и сортировки. Это предотвращает дублирование контента.
// app/products/page.tsx (с query-параметрами)
export async function generateMetadata({ searchParams }: Props): Promise<Metadata> {
// Например, URL: /products?sort=price&brand=nike
// Но основная версия — это /products
return {
alternates: {
canonical: '/products', // Игнорируем параметры сортировки/фильтров
},
};
}
Резюме для разработчика
- Canonical — это MUST-HAVE. Это директива, а не рекомендация. Без неё вы рискуете получить санкции за дублирующий контент и потерять в позициях.
- В App Router используйте alternates.canonical внутри generateMetadata.
- Всегда используйте абсолютные URL для динамических страниц, предварительно задав NEXT_PUBLIC_SITE_URL.
- Держите canonical в чистоте: URL должен быть основным, без UTM-меток, сессионных ID и параметров сортировки, которые не меняют сущность контента.
- Синхронизируйте alternates.canonical с openGraph.url.
Внедрение этой простой практики не займет много времени, но станет значительным вкладом в SEO-здоровье вашего Next.js-приложения. Не пренебрегайте ей.