Domů O mně Služby Portfolio Učím Blog Kontakt
← Zpět na učím

10 — Metadata (title, description, Open Graph)

Výuka

Proč to existuje?

Když sdílíš odkaz na Facebooku, Twitteru nebo v Google, zobrazí se náhled — titulek, popis, obrázek. Tato data se nazývají metadata a jsou klíčová pro:

  • SEO — Google používá title a description ve výsledcích vyhledávání
  • Sociální sítě — náhledy odkazů (Open Graph, Twitter Cards)
  • Záložky prohlížeče — title se zobrazí v tabu a záložkách
  • Přístupnost — screen readery čtou title stránky

Bez metadat by tvoje stránka měla generický název (např. "React App") a žádný popis — to je špatné pro marketing i SEO.

Jak to funguje?

V Next.js App Routeru definuješ metadata dvěma způsoby:

1. Static metadata — export objektu metadata

export const metadata = {
  title: 'Můj Blog',
  description: 'Články o programování'
}

2. Dynamic metadata — export funkce generateMetadata

export async function generateMetadata({ params }) {
  const post = await getPost(params.id)
  return {
    title: post.title,
    description: post.excerpt
  }
}

Next.js tyto informace automaticky vloží do HTML dokumentu.

K čemu to slouží?

  • Lepší SEO — vyšší pozice ve vyhledávání
  • Více kliků — atraktivní title a description = více návštěvníků
  • Profesionální vzhled — pěkné náhledy na sociálních sítích
  • Branding — konzistentní název napříč aplikací

JavaScript

// app/layout.js (globální metadata)
export const metadata = {
  title: {
    default: 'Moje Aplikace',
    template: '%s | Moje Aplikace'  // Pro podstránky
  },
  description: 'Nejlepší aplikace na světě',
  keywords: ['next.js', 'react', 'web development'],
}

// app/page.js (domovská stránka)
export const metadata = {
  title: 'Domů',  // Použije template: "Domů | Moje Aplikace"
}

// app/blog/page.js
export const metadata = {
  title: 'Blog',
  description: 'Články o programování a technologiích',
}

// app/blog/[slug]/page.js (dynamická metadata)
export async function generateMetadata({ params }) {
  const post = await fetch(`https://api.example.com/posts/${params.slug}`)
    .then(r => r.json())

  return {
    title: post.title,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      description: post.excerpt,
      images: [{ url: post.coverImage }],
    },
  }
}

export default async function BlogPost({ params }) {
  const post = await fetch(`https://api.example.com/posts/${params.slug}`)
    .then(r => r.json())

  return <article>{/* ... */}</article>
}

Open Graph (Facebook, LinkedIn)

export const metadata = {
  title: 'Můj Článek',
  description: 'Popis článku',
  openGraph: {
    title: 'Můj Článek',
    description: 'Popis článku',
    url: 'https://example.com/article',
    siteName: 'Můj Blog',
    images: [
      {
        url: 'https://example.com/og-image.jpg',
        width: 1200,
        height: 630,
      },
    ],
    locale: 'cs_CZ',
    type: 'article',
  },
}

Twitter Cards

export const metadata = {
  title: 'Můj Článek',
  twitter: {
    card: 'summary_large_image',
    title: 'Můj Článek',
    description: 'Popis článku',
    creator: '@mojTwitter',
    images: ['https://example.com/twitter-image.jpg'],
  },
}

TypeScript

// app/layout.tsx
import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: {
    default: 'Moje Aplikace',
    template: '%s | Moje Aplikace'
  },
  description: 'Nejlepší aplikace na světě',
  keywords: ['next.js', 'react', 'web development'],
  authors: [{ name: 'Jméno Příjmení' }],
  creator: 'Jméno Příjmení',
}

// app/blog/[slug]/page.tsx
import type { Metadata } from 'next'

interface Post {
  title: string
  excerpt: string
  coverImage: string
  author: string
}

interface PageProps {
  params: { slug: string }
}

export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
  const post: Post = await fetch(`https://api.example.com/posts/${params.slug}`)
    .then(r => r.json())

  return {
    title: post.title,
    description: post.excerpt,
    authors: [{ name: post.author }],
    openGraph: {
      title: post.title,
      description: post.excerpt,
      type: 'article',
      images: [
        {
          url: post.coverImage,
          width: 1200,
          height: 630,
          alt: post.title,
        },
      ],
    },
    twitter: {
      card: 'summary_large_image',
      title: post.title,
      description: post.excerpt,
      images: [post.coverImage],
    },
  }
}

export default async function BlogPost({ params }: PageProps): Promise<JSX.Element> {
  const post: Post = await fetch(`https://api.example.com/posts/${params.slug}`)
    .then(r => r.json())

  return (
    <article>
      <h1>{post.title}</h1>
      {/* ... */}
    </article>
  )
}

Rozdíl JS vs TS

TypeScript přidává:

  • Metadata typ z next — autocomplete všech možných polí
  • Type safety — nemůžeš omylem napsat špatný klíč
  • Dokumentaci — víš, jaké hodnoty jsou povolené (např. type: 'article')

Dostupná metadata pole

export const metadata: Metadata = {
  // Základní
  title: 'Titulek',
  description: 'Popis stránky',
  keywords: ['klíčové', 'slovo'],

  // Autoři
  authors: [{ name: 'Jméno', url: 'https://example.com' }],
  creator: 'Jméno',
  publisher: 'Firma',

  // Open Graph (Facebook, LinkedIn)
  openGraph: {
    title: 'OG Titulek',
    description: 'OG Popis',
    url: 'https://example.com',
    siteName: 'Název webu',
    images: [{ url: '/og-image.jpg', width: 1200, height: 630 }],
    locale: 'cs_CZ',
    type: 'website',  // nebo 'article', 'profile', atd.
  },

  // Twitter
  twitter: {
    card: 'summary_large_image',
    site: '@twitterHandle',
    creator: '@twitterHandle',
    title: 'Twitter Titulek',
    description: 'Twitter Popis',
    images: ['/twitter-image.jpg'],
  },

  // Robots (SEO)
  robots: {
    index: true,     // Povolit indexování
    follow: true,    // Následovat odkazy
    googleBot: {
      index: true,
      follow: true,
    },
  },

  // Ikony
  icons: {
    icon: '/favicon.ico',
    apple: '/apple-icon.png',
  },

  // Manifest (PWA)
  manifest: '/manifest.json',

  // Viewport
  viewport: 'width=device-width, initial-scale=1',

  // Theme color
  themeColor: '#ffffff',
}

Title template

// app/layout.js
export const metadata = {
  title: {
    default: 'Moje Aplikace',
    template: '%s | Moje Aplikace'  // %s se nahradí titulkem podstránky
  }
}

// app/about/page.js
export const metadata = {
  title: 'O nás'  // Výsledek: "O nás | Moje Aplikace"
}

// app/blog/[slug]/page.js
export async function generateMetadata({ params }) {
  const post = await getPost(params.slug)
  return {
    title: post.title  // Výsledek: "Název článku | Moje Aplikace"
  }
}

Tip

  • Title template — definuj v root layout pro konzistentní názvy
  • Drž metadata blízko obsahu — každá page může mít vlastní metadata
  • Open Graph obrázky — doporučená velikost 1200×630 px
  • Dynamic metadata — použij generateMetadata pro dynamický obsah (články, produkty)
  • Keywords jsou zastaralé — Google je nepoužívá, ale nezaškodí
  • Robotsnoindex pro admin stránky nebo duplicitní obsah

Validace Open Graph

Otestuj, jak vypadají tvé náhledy:

Kvíz

Kde je nejlepší místo pro definici metadata konkrétního článku na blogu?

Metadata pro dynamický obsah (jako je článek na blogu) by měla být definována v page komponentě pomocí funkce generateMetadata. Tato funkce se zavolá pro každý článek a můžeš v ní načíst data (titulek, popis, obrázek) z databáze nebo API a vrátit je jako metadata. Next.js pak automaticky vloží tato metadata do HTML dokumentu. To zajišťuje, že každý článek má vlastní SEO-optimalizovaný titulek a popis, a když ho někdo sdílí na sociálních sítích, zobrazí se správný náhled.

🎯 Závěrečný projekt

Po dokončení všech 8 dílů vytvoříte jednoduchou Todо aplikaci v čistém JavaScriptu. Naučíte se, jak aplikovat vše, co jste se naučili, na reálný projekt.

Zobrazit podrobnosti projektu →

Připraveni začít?

Zaregistrujte se a získejte přístup ke všem dílům tohoto seriálu

Kontaktujte mě

Informace o seriálu

Obtížnost

Délka

Cca 480 minut

Počet videí

8 videí + projekty

Certifikát

Po dokončení obdržíte certifikát


Lekce v této sekci


Struktura lekcí (souborový strom)

06. Typescript specifika
  • v přípravě
08. Moduly tridy
  • v přípravě
09. React zaklady
  • v přípravě
10. React hooks
  • v přípravě
12. Nextjs server
  • v přípravě
13. Databaze auth
  • v přípravě
14. Nextjs pokrocile
  • v přípravě