Загрузка...
Загрузка...
Next.js имеет встроенную систему управления метаданными для SEO. Метаданные можно задавать статически через объект metadata или динамически через функцию generateMetadata.
// app/layout.tsx
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Hack Frontend',
description: 'Подготовка к собеседованию по фронтенд-разработке',
keywords: ['фронтенд', 'собеседование', 'javascript', 'react']
}
Для страниц, где метаданные зависят от данных, используется generateMetadata:
// app/docs/[slug]/page.tsx
import type { Metadata } from 'next'
import { getDoc } from '@/lib/docs'
export async function generateMetadata({
params
}: {
params: { slug: string }
}): Promise<Metadata> {
const doc = await getDoc(params.slug)
return {
title: doc.title,
description: doc.description,
keywords: doc.keywords,
openGraph: {
title: doc.title,
description: doc.description,
type: 'article'
}
}
}
Next.js дедуплицирует запросы: если generateMetadata и компонент страницы вызывают один и тот же fetch, запрос выполнится один раз.
export const metadata: Metadata = {
title: 'JavaScript задачи с собеседований',
openGraph: {
title: 'JavaScript задачи с собеседований',
description: 'Реши задачи из реальных интервью',
url: 'https://hackfrontend.com/problems',
siteName: 'Hack Frontend',
images: [
{
url: '/images/HackFrontendBanner.png',
width: 1200,
height: 630
}
],
locale: 'ru_RU',
type: 'website'
},
twitter: {
card: 'summary_large_image',
title: 'JavaScript задачи с собеседований',
description: 'Реши задачи из реальных интервью',
images: ['/images/HackFrontendBanner.png']
}
}
Для добавления суффикса или префикса к заголовкам дочерних страниц:
// app/layout.tsx
export const metadata: Metadata = {
title: {
template: '%s | Hack Frontend',
default: 'Hack Frontend'
}
}
// app/docs/page.tsx
export const metadata: Metadata = {
title: 'База знаний'
}
// Результат: "База знаний | Hack Frontend"
Next.js может генерировать sitemap автоматически:
// app/sitemap.ts
import { MetadataRoute } from 'next'
export default function sitemap(): MetadataRoute.Sitemap {
const docs = getAllDocs()
return [
{ url: 'https://hackfrontend.com', lastModified: new Date() },
{ url: 'https://hackfrontend.com/problems', lastModified: new Date() },
...docs.map(doc => ({
url: `https://hackfrontend.com/docs/${doc.slug}`,
lastModified: doc.updatedAt
}))
]
}
// app/robots.ts
import { MetadataRoute } from 'next'
export default function robots(): MetadataRoute.Robots {
return {
rules: {
userAgent: '*',
allow: '/',
disallow: ['/api/', '/auth/']
},
sitemap: 'https://hackfrontend.com/sitemap.xml'
}
}
Для расширенных сниппетов в поиске:
// app/docs/[slug]/page.tsx
export default async function DocPage({ params }) {
const doc = await getDoc(params.slug)
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'Article',
headline: doc.title,
description: doc.description,
author: { '@type': 'Organization', name: 'Hack Frontend' }
}
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
<article>{doc.content}</article>
</>
)
}
На собеседовании:
Метаданные наследуются от родительского сегмента к дочернему и мержатся. Дочерний сегмент может переопределить любое поле родителя.