Metadata и SEO в Next.js

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, запрос выполнится один раз.

Open Graph и Twitter Cards

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"

Sitemap

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
    }))
  ]
}

robots.txt

// 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'
  }
}

Structured Data (JSON-LD)

Для расширенных сниппетов в поиске:

// 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>
    </>
  )
}

На собеседовании:

Метаданные наследуются от родительского сегмента к дочернему и мержатся. Дочерний сегмент может переопределить любое поле родителя.

Полезные ресурсы

Связанные темы