Sayfa İçi SEO Rehberi: Teknik Sayfa Yapısı ve Metadata
URL yapısı, başlık hiyerarşisi, meta tags, canonical ve görsel SEO. Next.js Metadata API ile otomatik optimizasyon teknikleri.
Sayfa İçi SEO Rehberi: Teknik Sayfa Yapısı ve Metadata
Teknik SEO, içeriğinizin arama motorları tarafından doğru şekilde taranmasını, anlaşılmasını ve indexlenmesini sağlar. Bu yazıda URL yapısı, başlık hiyerarşisi, meta tags, canonical etiketleri ve görsel SEO konularını Next.js kod örnekleriyle inceliyoruz.
Bu yazı, 5 parçalık Sayfa İçi SEO Rehberi serisinin ikinci bölümüdür. İlk bölümde Arama Niyeti ve Semantik SEO konularını ele almıştık.
İçindekiler
- URL Yapısı ve Best Practices
- Başlık Hiyerarşisi (H1-H6)
- Meta Tags Optimizasyonu
- Next.js Metadata API Derinlemesine
- Canonical Tags ve Duplicate Content
- Görsel SEO ve Optimizasyon
- Hreflang ve Çok Dilli Siteler
- Robots Meta Tags
- Pratik Checklist
- Sık Sorulan Sorular
- Sonuç
URL Yapısı ve Best Practices
URL yapısı, hem kullanıcı deneyimi hem de SEO için kritiktir. İyi bir URL, içeriğin ne hakkında olduğunu açıkça gösterir.
İdeal URL Anatomisi
https://beydemir.dev/blog/sayfa-ici-seo-teknik-yapi
↑ ↑ ↑
Domain Kategori Açıklayıcı Slug
URL Best Practices:
- Kısa ve açıklayıcı tutun (60 karakterin altında)
- Küçük harf kullanın
- Kelimeler arasında tire (-) kullanın
- Gereksiz parametrelerden kaçının
- Anahtar kelimeyi dahil edin
Next.js ile SEO-Friendly Slug Üretimi
Türkçe karakterleri ve özel karakterleri düzgün işleyen bir slug generator:
// lib/seo-utils.ts
export function generateSlug(title: string): string {
const turkishMap: Record<string, string> = {
'ç': 'c', 'ğ': 'g', 'ı': 'i', 'ö': 'o', 'ş': 's', 'ü': 'u',
'Ç': 'c', 'Ğ': 'g', 'İ': 'i', 'Ö': 'o', 'Ş': 's', 'Ü': 'u'
};
return title
.split('')
.map(char => turkishMap[char] || char)
.join('')
.toLowerCase()
.replace(/[^a-z0-9\s-]/g, '') // Özel karakterleri kaldır
.replace(/\s+/g, '-') // Boşlukları tire yap
.replace(/-+/g, '-') // Çoklu tireleri tekile indir
.replace(/^-|-$/g, '') // Baş ve sondaki tireleri kaldır
.slice(0, 60); // Maksimum uzunluk
}
// Kullanım
generateSlug('Sayfa İçi SEO: Türkçe Özel Karakterler!')
// Çıktı: "sayfa-ici-seo-turkce-ozel-karakterler"301 Redirect Yönetimi
URL değişikliklerinde 301 redirect zorunludur. Next.js'te:
// next.config.js
const nextConfig = {
async redirects() {
return [
{
source: '/eski-blog/:slug',
destination: '/blog/:slug',
permanent: true, // 301 redirect
},
{
source: '/yazilar/:year/:slug',
destination: '/blog/:slug',
permanent: true,
},
// Trailing slash standardizasyonu
{
source: '/blog/:slug/',
destination: '/blog/:slug',
permanent: true,
},
];
},
};
module.exports = nextConfig;Redirect Chain Detector
Redirect zincirleri SEO'yu olumsuz etkiler. Bunları tespit eden bir script:
// scripts/check-redirects.ts
async function checkRedirectChain(url: string, maxHops = 5): Promise<{
chain: string[];
finalUrl: string;
hops: number;
hasIssue: boolean;
}> {
const chain: string[] = [];
let currentUrl = url;
for (let i = 0; i < maxHops; i++) {
chain.push(currentUrl);
const response = await fetch(currentUrl, {
method: 'HEAD',
redirect: 'manual'
});
if (response.status >= 300 && response.status < 400) {
const location = response.headers.get('location');
if (location) {
currentUrl = new URL(location, currentUrl).href;
} else {
break;
}
} else {
break;
}
}
return {
chain,
finalUrl: currentUrl,
hops: chain.length - 1,
hasIssue: chain.length > 2 // 1'den fazla redirect varsa sorun
};
}
// Kullanım
const result = await checkRedirectChain('https://site.com/eski-sayfa');
if (result.hasIssue) {
console.warn(`Redirect zinciri bulundu: ${result.chain.join(' → ')}`);
}Başlık Hiyerarşisi (H1-H6)
Başlık hiyerarşisi, hem erişilebilirlik hem de SEO için kritiktir. Google, başlıkları içerik yapısını anlamak için kullanır.
Kurallar
- Her sayfada tek H1: Sayfa başlığını temsil eder
- Sıralı hiyerarşi: H1 → H2 → H3 (H2'den H4'e atlamayın)
- Anlamlı başlıklar: Anahtar kelimeleri doğal şekilde içermeli
Başlık Hiyerarşisi Validator
Projenizde başlık hatalarını yakalayan bir ESLint kuralı:
// eslint-rules/heading-hierarchy.ts
import type { Rule } from 'eslint';
const headingHierarchyRule: Rule.RuleModule = {
meta: {
type: 'suggestion',
docs: {
description: 'Başlık hiyerarşisinin SEO için doğru olmasını sağlar',
},
messages: {
skippedLevel: 'Başlık seviyesi atlandı: {{found}} bulundu, {{expected}} bekleniyordu',
multipleH1: 'Birden fazla H1 bulundu. Sayfa başına tek H1 kullanın',
},
},
create(context) {
let h1Count = 0;
let lastLevel = 0;
return {
JSXOpeningElement(node: any) {
const tagName = node.name?.name;
if (!tagName || !/^h[1-6]$/i.test(tagName)) return;
const level = parseInt(tagName[1]);
// Çoklu H1 kontrolü
if (level === 1) {
h1Count++;
if (h1Count > 1) {
context.report({
node,
messageId: 'multipleH1',
});
}
}
// Seviye atlama kontrolü
if (lastLevel > 0 && level > lastLevel + 1) {
context.report({
node,
messageId: 'skippedLevel',
data: {
found: `h${level}`,
expected: `h${lastLevel + 1}`,
},
});
}
lastLevel = level;
},
};
},
};
export default headingHierarchyRule;Meta Tags Optimizasyonu
Meta tags, arama sonuçlarında görünen içeriktir. CTR (Click-Through Rate) doğrudan etkiler.
Title Tag
Kurallar:
- 50-60 karakter arası (Google 60'tan sonra keser)
- Ana anahtar kelime başta
- Marka adı sonda (opsiyonel)
- Her sayfa için benzersiz
// lib/seo.ts
interface TitleConfig {
title: string;
brand?: string;
maxLength?: number;
}
export function optimizeTitle({ title, brand, maxLength = 60 }: TitleConfig): string {
const separator = ' | ';
const brandSuffix = brand ? `${separator}${brand}` : '';
// Brand ile birlikte maksimum uzunluğu aşıyorsa
if (title.length + brandSuffix.length > maxLength) {
// Brand'ı çıkar ve title'ı kısalt
const availableLength = maxLength - 3; // "..." için yer
return title.slice(0, availableLength) + '...';
}
return title + brandSuffix;
}
// Kullanım
optimizeTitle({
title: 'Sayfa İçi SEO Rehberi: Teknik Yapı ve Metadata',
brand: 'Beydemir.dev'
});
// Çıktı: "Sayfa İçi SEO Rehberi: Teknik Yapı ve Metadata | Beydemir.dev"Meta Description
Kurallar:
- 150-160 karakter arası
- Call-to-action içermeli
- Ana anahtar kelimeyi doğal şekilde kullanın
- Her sayfa için benzersiz
export function optimizeDescription(description: string, maxLength = 155): string {
if (description.length <= maxLength) {
return description;
}
// Son kelimede kesme, cümle sonunda kes
const truncated = description.slice(0, maxLength);
const lastSpace = truncated.lastIndexOf(' ');
return truncated.slice(0, lastSpace) + '...';
}Next.js Metadata API Derinlemesine
Next.js App Router, güçlü bir Metadata API sunar. Hem statik hem dinamik metadata desteği vardır.
Statik Metadata
// app/blog/page.tsx
import { Metadata } from 'next';
export const metadata: Metadata = {
title: 'Blog | Beydemir.dev',
description: 'Web geliştirme, SEO ve modern JavaScript hakkında yazılar.',
openGraph: {
title: 'Blog | Beydemir.dev',
description: 'Web geliştirme, SEO ve modern JavaScript hakkında yazılar.',
type: 'website',
},
};Dinamik Metadata
// app/blog/[slug]/page.tsx
import { Metadata } from 'next';
import { getPostBySlug } from '@/lib/mdx';
interface Props {
params: { slug: string };
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const post = await getPostBySlug(params.slug);
if (!post) {
return {
title: 'Sayfa Bulunamadı',
};
}
return {
title: post.title,
description: post.excerpt,
authors: [{ name: post.author }],
openGraph: {
title: post.title,
description: post.excerpt,
type: 'article',
publishedTime: post.date,
authors: [post.author],
images: [
{
url: post.imageUrl,
width: 1200,
height: 630,
alt: post.title,
},
],
},
twitter: {
card: 'summary_large_image',
title: post.title,
description: post.excerpt,
images: [post.imageUrl],
},
alternates: {
canonical: `/blog/${post.slug}`,
},
};
}Metadata Template Pattern
Layout seviyesinde template tanımlayarak tutarlılık sağlayın:
// app/layout.tsx
import { Metadata } from 'next';
export const metadata: Metadata = {
metadataBase: new URL('https://beydemir.dev'),
title: {
default: 'Beydemir.dev - Full Stack Developer',
template: '%s | Beydemir.dev', // Alt sayfalarda kullanılır
},
description: 'Web geliştirme ve SEO hakkında içerikler.',
robots: {
index: true,
follow: true,
},
};Alt sayfalarda sadece title verdiğinizde, template otomatik uygulanır:
// app/about/page.tsx
export const metadata = {
title: 'Hakkımda', // Sonuç: "Hakkımda | Beydemir.dev"
};Canonical Tags ve Duplicate Content
Canonical tag, aynı içeriğin farklı URL'lerde görünmesi durumunda ana versiyonu belirtir.
Ne Zaman Kullanılmalı?
- URL parametreleri:
?sort=date,?page=2 - HTTP vs HTTPS
- www vs non-www
- Trailing slash farklılıkları
- Syndicated içerik
Yaygın Hatalar
<!-- ❌ YANLIŞ: Relative URL -->
<link rel="canonical" href="/blog/post" />
<!-- ✅ DOĞRU: Absolute URL -->
<link rel="canonical" href="https://beydemir.dev/blog/post" />
<!-- ❌ YANLIŞ: HTTP (site HTTPS ise) -->
<link rel="canonical" href="http://beydemir.dev/blog/post" />
<!-- ✅ DOĞRU: HTTPS -->
<link rel="canonical" href="https://beydemir.dev/blog/post" />Next.js'te Canonical Ayarlama
// app/blog/[slug]/page.tsx
export async function generateMetadata({ params }: Props): Promise<Metadata> {
return {
alternates: {
canonical: `https://beydemir.dev/blog/${params.slug}`,
},
};
}Pagination ve Canonical
Sayfalandırılmış içeriklerde dikkatli olun:
// app/blog/page.tsx
export async function generateMetadata({ searchParams }: Props): Promise<Metadata> {
const page = parseInt(searchParams.page || '1');
return {
// Sayfa 2+ için noindex, ama canonical her zaman sayfa 1
robots: page > 1 ? { index: false, follow: true } : undefined,
alternates: {
canonical: 'https://beydemir.dev/blog', // Her zaman ana sayfa
},
};
}Görsel SEO ve Optimizasyon
Görsel SEO, hem Google Images sıralaması hem de sayfa performansı için kritiktir.
Alt Text Best Practices
- Açıklayıcı olun: Görselin ne gösterdiğini söyleyin
- 125 karakterin altında tutun
- Anahtar kelimeyi doğal şekilde kullanın
- Dekoratif görseller için boş bırakın (
alt="")
Next.js Image Component ile SEO
// components/SEOImage.tsx
import Image from 'next/image';
interface SEOImageProps {
src: string;
alt: string;
title?: string;
priority?: boolean;
caption?: string;
}
export function SEOImage({
src,
alt,
title,
priority = false,
caption
}: SEOImageProps) {
return (
<figure>
<Image
src={src}
alt={alt}
title={title || alt}
width={800}
height={450}
priority={priority} // Above-the-fold görseller için
loading={priority ? undefined : 'lazy'}
sizes="(max-width: 768px) 100vw, 800px"
quality={85}
style={{ width: '100%', height: 'auto' }}
/>
{caption && (
<figcaption className="text-sm text-gray-500 mt-2 text-center">
{caption}
</figcaption>
)}
</figure>
);
}Görsel Dosya Adlandırma
❌ Kötü: IMG_20240101_123456.jpg
❌ Kötü: image1.png
✅ İyi: sayfa-ici-seo-url-yapisi.webp
✅ İyi: next-js-metadata-api.png
Hreflang ve Çok Dilli Siteler
Çok dilli sitelerde hreflang, Google'a hangi dilde hangi sayfa olduğunu söyler.
Next.js'te Hreflang
// app/layout.tsx
export const metadata: Metadata = {
alternates: {
languages: {
'tr': 'https://beydemir.dev',
'en': 'https://beydemir.dev/en',
'x-default': 'https://beydemir.dev/en', // Varsayılan dil
},
},
};Yaygın Hreflang Hataları
- x-default eksik: Varsayılan dili belirtin
- Tek yönlü linkler: Her dil diğer tüm dillere link vermeli
- Yanlış dil kodu:
tr-TRdeğiltrkullanın
Robots Meta Tags
Sayfa seviyesinde tarama kontrolü için robots meta tag kullanılır.
Temel Değerler
// Varsayılan (indexle ve linkleri takip et)
robots: { index: true, follow: true }
// Indexleme (arama, filtre, kullanıcı içeriği sayfaları)
robots: { index: false, follow: true }
// Hiçbir şey yapma
robots: { index: false, follow: false }Gelişmiş Ayarlar
export const metadata: Metadata = {
robots: {
index: true,
follow: true,
nocache: false,
googleBot: {
index: true,
follow: true,
'max-video-preview': -1, // Video önizleme limitsiz
'max-image-preview': 'large', // Büyük görsel önizleme
'max-snippet': -1, // Snippet limitsiz
},
},
};API Routes için X-Robots-Tag
// next.config.js
module.exports = {
async headers() {
return [
{
source: '/api/:path*',
headers: [
{
key: 'X-Robots-Tag',
value: 'noindex, nofollow',
},
],
},
];
},
};Pratik Checklist
Her sayfa için kontrol edin:
- URL 60 karakterden kısa ve SEO-friendly
- Tek H1 var ve sayfa başlığını temsil ediyor
- Başlık hiyerarşisi atlanmamış (H1 → H2 → H3)
- Title tag 50-60 karakter, anahtar kelime başta
- Meta description 150-160 karakter, CTA içeriyor
- Canonical tag mevcut ve absolute URL
- Tüm görsellerde anlamlı alt text
- Görsel dosya adları açıklayıcı
- Open Graph meta tags ekli
- Twitter Card meta tags ekli
- Robots meta tag uygun şekilde ayarlı
Sık Sorulan Sorular
Title ve H1 aynı mı olmalı?
Aynı olabilir ama zorunlu değil. Title arama sonuçlarında, H1 sayfa içinde görünür. Title'da marka adı olabilir, H1'de olmayabilir. Önemli olan ikisinin de ana konuyu yansıtması.
Canonical tag'ı kendine mi göstermeli?
Evet. Self-referencing canonical önerilir. Sayfa https://site.com/blog/post ise canonical de aynısını göstermeli. Bu, URL varyasyonlarından korur.
Her görsele alt text zorunlu mu?
Fonksiyonel görseller için evet. Dekoratif görseller (arka plan desenleri, ayırıcılar) için alt="" kullanın. Boş alt, görselin dekoratif olduğunu belirtir.
Meta description sıralamayı etkiler mi?
Doğrudan hayır. Ama CTR'ı etkiler, CTR da dolaylı olarak sıralamayı etkiler. İyi bir description daha fazla tıklanma demek.
URL'deki tarih SEO'yu olumsuz etkiler mi?
Zorunlu olarak hayır, ama gereksiz uzunluk ekler. /blog/2024/01/post yerine /blog/post tercih edin. Tarih gerekiyorsa içerikte gösterin.
Sonuç
Teknik sayfa yapısı, SEO'nun temelini oluşturur. Bu yazıda öğrendikleriniz:
- URL yapısı kısa, açıklayıcı ve tutarlı olmalı
- Başlık hiyerarşisi erişilebilirlik ve SEO için kritik
- Meta tags CTR'ı doğrudan etkiler
- Canonical tags duplicate content sorununu çözer
- Görsel SEO hem sıralama hem performans için önemli
Bir sonraki yazıda, İçerik Mimarisi ve Internal Linking konularını inceleyeceğiz: Silo yapısı, topic clusters, link equity ve content decay.
Seri Navigasyonu:
- Arama Niyeti ve Semantik SEO
- Teknik Sayfa Yapısı ve Metadata (Bu yazı)
- İçerik Mimarisi ve Internal Linking
- Schema.org ve JSON-LD
- JavaScript SEO ve Performans
Projenizi Hayata Geçirelim
Web sitesi, mobil uygulama veya yapay zeka çözümü mü arıyorsunuz? Fikirlerinizi birlikte değerlendirelim.
Ücretsiz Danışmanlık Alın