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á:
Metadatatyp znext— 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
generateMetadatapro dynamický obsah (články, produkty) - Keywords jsou zastaralé — Google je nepoužívá, ale nezaškodí
- Robots —
noindexpro admin stránky nebo duplicitní obsah
Validace Open Graph
Otestuj, jak vypadají tvé náhledy:
- Facebook: https://developers.facebook.com/tools/debug/
- Twitter: https://cards-dev.twitter.com/validator
- LinkedIn: https://www.linkedin.com/post-inspector/
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 →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
- 01 — Co je Next.js
- 02 — Vytvoření projektu
- 03 — Struktura projektu (app/)
- 04 — Page komponenty (page.js / page.tsx)
- 05 — Layout komponenty (layout.js / layout.tsx)
- 06 — File-based routing
- 07 — Dynamické routy ([id]/page.js)
- 08 — Link komponenta (navigace)
- 09 — Image komponenta (next/image)
- 10 — Metadata (title, description, Open Graph)
- 11 — Loading UI (loading.js / loading.tsx)
- 12 — Error handling (error.js / error.tsx)
- 13 — Not Found (not-found.js / not-found.tsx)
Struktura lekcí (souborový strom)
- 1.1 Úvod do JavaScriptu a TypeScriptu
- 1.2 Nastavení prostředí
- 1.3 První program
- 1.4 Proměnné: var, let, const
- 1.5 Datové typy - přehled
- 1.6 String (řetězce)
- 1.7 Number (čísla)
- 1.8 Boolean (pravda/nepravda)
- 1.9 Null a Undefined
- 1.10 Type Inference vs Annotations
- 1.11 Aritmetické operátory
- 1.12 Porovnávací operátory
- 1.13 Logické operátory
- 1.14 Komentáře
- 1.15 Console metody
- 03.01 Deklarace funkce
- 03.02 Function Expression
- 03.03 Arrow Functions
- 03.04 Parametry a argumenty
- 03.05 Return hodnoty
- 03.06 Výchozí parametry
- 03.07 Rest parametry
- 03.08 Co je Scope
- 03.09 Lexikální Scope
- 03.10 Řetězec Scope
- 03.11 Globální Scope
- 03.12 Životní Cyklus Proměnných
- 03.13 Omezení Scope
- 03.14 Použití Closures
- 03.15 Callback funkce
- 03.16 Higher-order Functions
- 03.17 IIFE
- 03.18 Void funkce
- 03.19 Rekurze
- v přípravě
- v přípravě
- v přípravě
- v přípravě
- 01 — Co je Next.js
- 02 — Vytvoření projektu
- 03 — Struktura projektu (app/)
- 04 — Page komponenty (page.js / page.tsx)
- 05 — Layout komponenty (layout.js / layout.tsx)
- 06 — File-based routing
- 07 — Dynamické routy ([id]/page.js)
- 08 — Link komponenta (navigace)
- 09 — Image komponenta (next/image)
- 10 — Metadata (title, description, Open Graph)
- 11 — Loading UI (loading.js / loading.tsx)
- 12 — Error handling (error.js / error.tsx)
- 13 — Not Found (not-found.js / not-found.tsx)
- v přípravě
- v přípravě
- v přípravě