Next.js Metadata API ile SEO Optimizasyonu

Next.js Metadata API ile SEO Optimizasyonu
Arama motoru optimizasyonu (SEO), web uygulamalarının keşfedilebilirliği için kritik öneme sahiptir. Next.js, Metadata API ile title, description, Open Graph, Twitter Card ve daha birçok meta etiketi kolayca yönetmenizi sağlar. Bu rehberde Next.js'in Metadata API'sini kullanarak SEO'yu nasıl optimize edeceğinizi adım adım göstereceğiz.
Statik Metadata
En basit metadata tanımı, bir layout veya page dosyasından metadata objesi export etmektir:
// app/layout.tsx
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: {
default: 'Tarik Tunç Blog',
template: '%s | Tarik Tunç Blog', // Alt sayfalarda kullanılır
},
description: 'Web geliştirme, React ve Next.js hakkında Türkçe blog yazıları',
keywords: ['Next.js', 'React', 'Web Geliştirme', 'TypeScript'],
authors: [{ name: 'Tarik Tunç', url: 'https://tariktunc.com' }],
creator: 'Tarik Tunç',
metadataBase: new URL('https://tariktunc.com'),
openGraph: {
type: 'website',
locale: 'tr_TR',
url: 'https://tariktunc.com',
siteName: 'Tarik Tunç Blog',
title: 'Tarik Tunç Blog',
description: 'Web geliştirme hakkında Türkçe içerikler',
images: [
{
url: '/og-image.png',
width: 1200,
height: 630,
alt: 'Tarik Tunç Blog',
},
],
},
twitter: {
card: 'summary_large_image',
title: 'Tarik Tunç Blog',
description: 'Web geliştirme hakkında Türkçe içerikler',
creator: '@tariktunc',
},
robots: {
index: true,
follow: true,
googleBot: {
index: true,
follow: true,
'max-video-preview': -1,
'max-image-preview': 'large',
'max-snippet': -1,
},
},
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="tr">
<body>{children}</body>
</html>
);
}
Dinamik Metadata
Dinamik sayfalarda generateMetadata fonksiyonu ile parametrelere göre metadata oluşturabilirsiniz:
// app/blog/[slug]/page.tsx
import type { Metadata } from 'next';
async function getPost(slug: string) {
const res = await fetch(`https://api.example.com/posts/${slug}`);
return res.json();
}
export async function generateMetadata({
params,
}: {
params: { slug: string };
}): Promise<Metadata> {
const post = await getPost(params.slug);
return {
title: post.title, // template ile: "Yazı Başlığı | Tarik Tunç Blog"
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
type: 'article',
publishedTime: post.publishedAt,
modifiedTime: post.updatedAt,
authors: [post.author.name],
tags: post.tags,
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],
},
alternates: {
canonical: `/blog/${params.slug}`,
},
};
}
generateMetadata içindeki fetch istekleri, aynı page component'teki fetch istekleri ile otomatik olarak deduplicate edilir. Yani aynı URL'ye iki kez istek gönderilmez.
Title Template
Layout'ta tanımlanan title template, alt sayfalarda otomatik uygulanır:
// app/layout.tsx
export const metadata: Metadata = {
title: {
default: 'Tarik Tunç Blog', // Fallback
template: '%s | Tarik Tunç Blog', // Alt sayfalar için
absolute: 'Özel Başlık', // Template'i yoksayar
},
};
// app/about/page.tsx
export const metadata: Metadata = {
title: 'Hakkımda',
// Sonuç: "Hakkımda | Tarik Tunç Blog"
};
// app/contact/page.tsx
export const metadata: Metadata = {
title: {
absolute: 'İletişim Sayfası', // Template uygulanmaz
},
// Sonuç: "İletişim Sayfası"
};
JSON-LD Structured Data
Arama motorlarının içeriğinizi daha iyi anlaması için JSON-LD yapılandırılmış veri ekleyin:
// app/blog/[slug]/page.tsx
export default async function BlogPost({ params }: { params: { slug: string } }) {
const post = await getPost(params.slug);
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'BlogPosting',
headline: post.title,
description: post.excerpt,
image: post.coverImage,
datePublished: post.publishedAt,
dateModified: post.updatedAt,
author: {
'@type': 'Person',
name: post.author.name,
url: post.author.url,
},
publisher: {
'@type': 'Organization',
name: 'Tarik Tunç Blog',
logo: {
'@type': 'ImageObject',
url: 'https://tariktunc.com/logo.png',
},
},
};
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
</>
);
}
Canonical URL ve Alternates
// app/blog/[slug]/page.tsx
export async function generateMetadata({ params }): Promise<Metadata> {
return {
alternates: {
canonical: `/blog/${params.slug}`,
languages: {
'tr-TR': `/tr/blog/${params.slug}`,
'en-US': `/en/blog/${params.slug}`,
},
types: {
'application/rss+xml': '/feed.xml',
},
},
};
}
Favicon ve Icons
// app/layout.tsx
export const metadata: Metadata = {
icons: {
icon: [
{ url: '/favicon.ico', sizes: 'any' },
{ url: '/icon.svg', type: 'image/svg+xml' },
],
apple: [
{ url: '/apple-icon.png', sizes: '180x180' },
],
shortcut: '/shortcut-icon.png',
},
manifest: '/manifest.json',
};
Verification ve Diğer Meta Etiketler
export const metadata: Metadata = {
verification: {
google: 'google-site-verification-code',
yandex: 'yandex-verification-code',
other: {
'msvalidate.01': 'bing-verification-code',
},
},
category: 'technology',
other: {
'theme-color': '#000000',
},
};
SEO Kontrol Listesi
- Her sayfada benzersiz title ve description olmalı
- Open Graph ve Twitter Card meta etiketleri eklenmeli
- Canonical URL tanımlanmalı
- JSON-LD structured data eklenmeli
- Sitemap.xml ve robots.txt oluşturulmalı
- Resimler için alt text kullanılmalı
- Semantic HTML etiketleri tercih edilmeli
- Core Web Vitals metrikleri optimize edilmeli
Sonuç
Next.js Metadata API, SEO optimizasyonunu framework seviyesinde ele alarak geliştiricilerin işini büyük ölçüde kolaylaştırır. Statik ve dinamik metadata desteği, title template, JSON-LD entegrasyonu ve otomatik deduplication gibi özelliklerle arama motoru görünürlüğünüzü maksimize edebilirsiniz. Her sayfanız için doğru metadata tanımlamayı alışkanlık haline getirin.