Sayfa İçi SEO Rehberi: JavaScript SEO ve Performans
SSR vs CSR, Core Web Vitals optimizasyonu, crawl budget yönetimi, edge SEO ve middleware. Modern web için ileri düzey SEO teknikleri.
Sayfa İçi SEO Rehberi: JavaScript SEO ve Performans
Modern web uygulamaları JavaScript ağırlıklıdır. React, Vue, Angular gibi frameworkler muhteşem kullanıcı deneyimleri sunar ama SEO açısından dikkat gerektirir. Bu yazıda rendering stratejileri, Core Web Vitals, crawl budget ve edge SEO konularını derinlemesine inceliyoruz.
Bu yazı, 5 parçalık Sayfa İçi SEO Rehberi serisinin son bölümüdür. Önceki yazılarda Arama Niyeti, Teknik Yapı, İçerik Mimarisi ve Schema Markup konularını ele almıştık.
İçindekiler
- JavaScript ve Googlebot
- SSR vs CSR vs ISR vs SSG
- Hydration ve SEO Etkileri
- Core Web Vitals Optimizasyonu
- Tarama Bütçesi (Crawl Budget)
- robots.txt Gelişmiş Kalıpları
- Edge SEO ve Middleware
- Performance Budgets ve CI/CD
- SEO Debugging Senaryoları
- Pratik Checklist
- Sık Sorulan Sorular
- Sonuç
JavaScript ve Googlebot
Googlebot, JavaScript'i render edebilir ama süreç anında değildir.
Two-Wave Indexing
Googlebot sayfaları iki aşamada işler:
| Aşama | Ne Olur | Süre |
|---|---|---|
| 1. İlk Tarama | Googlebot sayfayı indirir, statik HTML'i okur, linkleri keşfeder | Anında |
| 2. Render Taraması | JavaScript execute edilir, dinamik içerik indexlenir | Dakikalar → Haftalar |
Sorun: JavaScript ile render edilen içerik, günler hatta haftalar sonra indexlenebilir.
Çözüm: Kritik içeriği server-side render edin.
Googlebot'un JavaScript Limitleri
- Timeout: Render için ~5 saniye limit
- Resources: Bazı 3rd party scriptler bloklanabilir
- Crawl budget: JS render, ekstra kaynak tüketir
SSR vs CSR vs ISR vs SSG
Her rendering stratejisinin SEO etkileri farklıdır.
Karşılaştırma Tablosu
| Strateji | SEO | TTFB | Güncellik | Kullanım |
|---|---|---|---|---|
| SSG | Mükemmel | Çok hızlı | Build-time | Blog, dokümantasyon |
| ISR | Mükemmel | Hızlı | Periyodik | Ürün sayfaları, haberler |
| SSR | İyi | Yavaş | Gerçek zamanlı | Kişiselleştirilmiş içerik |
| CSR | Zayıf | Hızlı (shell) | Gerçek zamanlı | Dashboard, SPA |
SSG (Static Site Generation)
// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
const posts = await getAllPosts();
return posts.map(post => ({
slug: post.slug,
}));
}
// Build time'da HTML oluşturulur
export default async function BlogPost({ params }: Props) {
const post = await getPostBySlug(params.slug);
return <article>{post.content}</article>;
}SEO Avantajı: Pre-rendered HTML, bot hemen okur.
ISR (Incremental Static Regeneration)
// app/products/[id]/page.tsx
export const revalidate = 3600; // 1 saat
export default async function ProductPage({ params }: Props) {
const product = await getProduct(params.id);
return <ProductDetails product={product} />;
}SEO Avantajı: Statik hız + periyodik güncelleme.
Hybrid Rendering Pattern
Kritik içerik SSR, interaktif bölümler CSR:
// app/products/[id]/page.tsx
import { Suspense } from 'react';
export default async function ProductPage({ params }: Props) {
const product = await getProduct(params.id);
return (
<>
{/* SSR: SEO için kritik */}
<ProductInfo product={product} />
<ProductDescription description={product.description} />
{/* CSR: İnteraktif, SEO için kritik değil */}
<Suspense fallback={<PriceSkeleton />}>
<LivePrice productId={params.id} />
</Suspense>
<Suspense fallback={<ReviewsSkeleton />}>
<CustomerReviews productId={params.id} />
</Suspense>
</>
);
}Hydration ve SEO Etkileri
Hydration, server-rendered HTML'e JavaScript interaktivitesi ekleme sürecidir.
Hydration Problemleri
- Hydration mismatch: Server ve client HTML'i farklıysa hata oluşur
- Time to Interactive (TTI): Büyük JS bundle'ları gecikme yaratır
- CLS (Layout Shift): Hydration sırasında layout kaymaları
React Server Components ile Çözüm
// Server Component - Hydration gerektirmez
async function ArticleContent({ slug }: { slug: string }) {
const content = await getPostContent(slug);
return (
<article className="prose">
{content}
</article>
);
}
// Client Component - Sadece bu hydrate edilir
'use client';
function ShareButtons() {
const [copied, setCopied] = useState(false);
const handleShare = () => {
navigator.clipboard.writeText(window.location.href);
setCopied(true);
};
return (
<button onClick={handleShare}>
{copied ? 'Kopyalandı!' : 'Paylaş'}
</button>
);
}
// Kombine kullanım
export default function BlogPost({ params }) {
return (
<>
<ArticleContent slug={params.slug} />
<ShareButtons /> {/* Sadece bu hydrate edilir */}
</>
);
}Core Web Vitals Optimizasyonu
Core Web Vitals, Google'ın sayfa deneyimi sıralama faktörleridir.
LCP (Largest Contentful Paint)
En büyük içerik öğesinin render süresi. Hedef: < 2.5 saniye
Optimizasyon teknikleri:
// 1. Priority loading for hero image
import Image from 'next/image';
<Image
src="/hero.jpg"
alt="Hero"
priority // LCP elementi için
fetchPriority="high"
/>
// 2. Preconnect to external resources
// app/layout.tsx
<head>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://images.unsplash.com" />
</head>
// 3. Inline critical CSS (Next.js handles automatically)
// 4. Avoid render-blocking resourcesCLS (Cumulative Layout Shift)
Görsel kayma skoru. Hedef: < 0.1
Optimizasyon teknikleri:
// 1. Image dimensions
<Image
src="/photo.jpg"
width={800}
height={600}
alt="Photo"
/>
// 2. Font loading strategy
// next.config.js
import { Inter } from 'next/font/google';
const inter = Inter({
subsets: ['latin'],
display: 'swap', // FOUT önlemi
});
// 3. Skeleton loaders
function ProductCard() {
return (
<div className="w-full h-[200px]"> {/* Sabit yükseklik */}
<Suspense fallback={<ProductSkeleton />}>
<ProductContent />
</Suspense>
</div>
);
}
// 4. Reserve space for dynamic content
<div className="min-h-[300px]">
{/* Reklam alanı için yer ayır */}
<AdBanner />
</div>INP (Interaction to Next Paint)
Kullanıcı etkileşimine yanıt süresi. Hedef: < 200ms
Optimizasyon teknikleri:
// 1. Debounce expensive operations
import { useDebouncedCallback } from 'use-debounce';
function SearchInput() {
const handleSearch = useDebouncedCallback((term: string) => {
// API call
}, 300);
return <input onChange={e => handleSearch(e.target.value)} />;
}
// 2. Use Web Workers for heavy computation
// workers/heavy-calc.ts
self.onmessage = (e) => {
const result = expensiveCalculation(e.data);
self.postMessage(result);
};
// 3. Virtualize long lists
import { useVirtualizer } from '@tanstack/react-virtual';
function VirtualList({ items }) {
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
});
// ...
}
// 4. Code splitting for heavy components
const HeavyChart = dynamic(() => import('./HeavyChart'), {
loading: () => <ChartSkeleton />,
});Tarama Bütçesi (Crawl Budget)
Googlebot'un sitenizi taramak için ayırdığı kaynak miktarıdır.
Crawl Budget'ı Etkileyen Faktörler
- Site hızı: Yavaş siteler daha az taranır
- Sunucu hataları: 5xx hatalar bütçeyi israf eder
- Duplicate content: Aynı içeriğin farklı URL'leri
- Düşük kaliteli sayfalar: Değersiz sayfalar
Optimizasyon Stratejileri
// 1. Gereksiz URL'leri engelle
// app/robots.ts
export default function robots(): MetadataRoute.Robots {
return {
rules: [
{
userAgent: 'Googlebot',
allow: '/',
disallow: [
'/api/',
'/admin/',
'/*?sort=*',
'/*?filter=*',
'/*?page=*', // Pagination (canonical kullan)
'/search',
'/tag/*', // Eğer tag sayfaları değersizse
],
},
],
sitemap: 'https://beydemir.dev/sitemap.xml',
};
}Log Analizi ile Crawl Insights
// scripts/analyze-crawl.ts
interface CrawlLog {
timestamp: Date;
userAgent: string;
path: string;
status: number;
responseTime: number;
}
async function analyzeCrawlLogs(logs: CrawlLog[]) {
const botLogs = logs.filter(log =>
log.userAgent.includes('Googlebot') ||
log.userAgent.includes('bingbot')
);
const analysis = {
totalCrawls: botLogs.length,
byBot: groupBy(botLogs, 'userAgent'),
errorRate: botLogs.filter(l => l.status >= 400).length / botLogs.length,
avgResponseTime: average(botLogs.map(l => l.responseTime)),
mostCrawled: getMostFrequent(botLogs.map(l => l.path)),
wastedCrawls: botLogs.filter(l =>
l.path.includes('?') || // Query params
l.status === 404 || // Not found
l.status === 301 // Redirects
).length,
};
console.log('Crawl Analysis:');
console.log(`Total: ${analysis.totalCrawls}`);
console.log(`Error Rate: ${(analysis.errorRate * 100).toFixed(1)}%`);
console.log(`Avg Response: ${analysis.avgResponseTime.toFixed(0)}ms`);
console.log(`Wasted: ${analysis.wastedCrawls}`);
return analysis;
}robots.txt Gelişmiş Kalıpları
// app/robots.ts
import { MetadataRoute } from 'next';
export default function robots(): MetadataRoute.Robots {
const isProduction = process.env.NODE_ENV === 'production';
// Development/staging'de indexleme engelle
if (!isProduction) {
return {
rules: {
userAgent: '*',
disallow: '/',
},
};
}
return {
rules: [
{
userAgent: 'Googlebot',
allow: '/',
disallow: [
'/api/',
'/admin/',
'/private/',
'/*.json$', // JSON dosyaları
'/*?*', // Tüm query params
],
},
{
userAgent: 'Googlebot-Image',
allow: '/images/',
disallow: '/private-images/',
},
{
userAgent: 'GPTBot', // ChatGPT crawler
disallow: '/', // AI training'i engelle
},
{
userAgent: 'CCBot', // Common Crawl
disallow: '/',
},
{
userAgent: '*', // Diğer botlar
allow: '/',
disallow: ['/api/', '/admin/'],
},
],
sitemap: 'https://beydemir.dev/sitemap.xml',
host: 'https://beydemir.dev',
};
}Edge SEO ve Middleware
Edge functions, sunucu yanıtlarını kullanıcıya yakın lokasyonlarda işler.
Next.js Middleware ile SEO
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const response = NextResponse.next();
const { pathname, hostname } = request.nextUrl;
// 1. www → non-www redirect
if (hostname.startsWith('www.')) {
const newUrl = request.nextUrl.clone();
newUrl.hostname = hostname.replace('www.', '');
return NextResponse.redirect(newUrl, 301);
}
// 2. Küçük harf URL'ler
if (pathname !== pathname.toLowerCase()) {
const newUrl = request.nextUrl.clone();
newUrl.pathname = pathname.toLowerCase();
return NextResponse.redirect(newUrl, 301);
}
// 3. Trailing slash kaldır
if (pathname.endsWith('/') && pathname !== '/') {
const newUrl = request.nextUrl.clone();
newUrl.pathname = pathname.slice(0, -1);
return NextResponse.redirect(newUrl, 301);
}
// 4. Security headers
response.headers.set('X-Content-Type-Options', 'nosniff');
response.headers.set('X-Frame-Options', 'DENY');
response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
// 5. Bot detection (opsiyonel logging)
const userAgent = request.headers.get('user-agent') || '';
if (userAgent.includes('Googlebot')) {
response.headers.set('X-Served-For', 'googlebot');
// Log crawl event
}
return response;
}
export const config = {
matcher: [
'/((?!_next/static|_next/image|favicon.ico|robots.txt|sitemap.xml).*)',
],
};Performance Budgets ve CI/CD
Lighthouse CI Kurulumu
// lighthouserc.js
module.exports = {
ci: {
collect: {
url: [
'http://localhost:3000/',
'http://localhost:3000/blog',
'http://localhost:3000/blog/sayfa-ici-seo-arama-niyeti-semantik',
],
startServerCommand: 'npm run start',
numberOfRuns: 3,
},
assert: {
assertions: {
// Performance
'categories:performance': ['error', { minScore: 0.9 }],
'first-contentful-paint': ['warn', { maxNumericValue: 2000 }],
'largest-contentful-paint': ['error', { maxNumericValue: 2500 }],
'cumulative-layout-shift': ['error', { maxNumericValue: 0.1 }],
// SEO
'categories:seo': ['error', { minScore: 0.95 }],
'document-title': 'error',
'meta-description': 'error',
'http-status-code': 'error',
'is-crawlable': 'error',
'robots-txt': 'error',
'canonical': 'error',
// Accessibility
'categories:accessibility': ['warn', { minScore: 0.9 }],
},
},
upload: {
target: 'temporary-public-storage',
},
},
};GitHub Actions Workflow
# .github/workflows/lighthouse.yml
name: Lighthouse CI
on:
pull_request:
branches: [main]
jobs:
lighthouse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Run Lighthouse CI
uses: treosh/lighthouse-ci-action@v10
with:
configPath: './lighthouserc.js'
uploadArtifacts: true
- name: Run SEO Audit
run: npm run seo:auditSEO Debugging Senaryoları
"Sayfam Neden Indexlenmiyor?"
// scripts/debug-indexing.ts
async function debugIndexingIssue(url: string) {
const checks = {
httpStatus: await checkHttpStatus(url),
robotsTxt: await isBlockedByRobots(url),
metaRobots: await getMetaRobots(url),
canonical: await getCanonical(url),
internalLinks: await countInternalLinksTo(url),
inSitemap: await isInSitemap(url),
loadTime: await measureLoadTime(url),
jsRendered: await checkJSContent(url),
};
const issues: string[] = [];
if (checks.httpStatus !== 200) {
issues.push(`HTTP Status: ${checks.httpStatus}`);
}
if (checks.robotsTxt) {
issues.push('robots.txt tarafından engelleniyor');
}
if (checks.metaRobots?.includes('noindex')) {
issues.push('noindex meta tag mevcut');
}
if (checks.canonical && checks.canonical !== url) {
issues.push(`Canonical farklı URL'ye işaret ediyor: ${checks.canonical}`);
}
if (checks.internalLinks < 3) {
issues.push(`Orphan sayfa - sadece ${checks.internalLinks} internal link`);
}
if (!checks.inSitemap) {
issues.push('Sitemap\'te yok');
}
if (checks.loadTime > 5000) {
issues.push(`Yavaş yükleme: ${checks.loadTime}ms`);
}
console.log('Indexleme Debug Raporu:');
console.log('URL:', url);
console.log('Kontroller:', checks);
console.log('Sorunlar:', issues.length > 0 ? issues : 'Sorun bulunamadı');
return { checks, issues };
}"Rich Snippet Görünmüyor"
// scripts/debug-rich-snippets.ts
async function debugRichSnippets(url: string) {
const response = await fetch(url);
const html = await response.text();
// JSON-LD schema'ları çıkar
const schemaRegex = /<script type="application\/ld\+json">([\s\S]*?)<\/script>/g;
const schemas: object[] = [];
let match;
while ((match = schemaRegex.exec(html)) !== null) {
try {
schemas.push(JSON.parse(match[1]));
} catch (e) {
console.error('Schema parse hatası:', e);
}
}
const issues: string[] = [];
// Article schema kontrolü
const articleSchema = schemas.find((s: any) => s['@type'] === 'Article');
if (articleSchema) {
const a = articleSchema as any;
if (!a.headline) issues.push('Article: headline eksik');
if (!a.image) issues.push('Article: image eksik (rich snippet için gerekli)');
if (!a.datePublished) issues.push('Article: datePublished eksik');
if (!a.author) issues.push('Article: author eksik');
}
// FAQ schema kontrolü
const faqSchema = schemas.find((s: any) => s['@type'] === 'FAQPage');
if (faqSchema) {
const f = faqSchema as any;
if (!f.mainEntity || f.mainEntity.length === 0) {
issues.push('FAQ: mainEntity boş');
}
}
console.log('Rich Snippet Debug Raporu:');
console.log('URL:', url);
console.log('Bulunan schema\'lar:', schemas.map((s: any) => s['@type']));
console.log('Sorunlar:', issues.length > 0 ? issues : 'Sorun bulunamadı');
return { schemas, issues };
}Pratik Checklist
- Kritik içerik SSR veya SSG ile render ediliyor
- Client-only component'lar lazy load ediliyor
- LCP elementi için priority loading uygulandı
- Görsellerde width/height tanımlı (CLS)
- Font loading optimize edildi (display: swap)
- robots.txt gereksiz URL'leri engelliyor
- Sitemap.xml güncel ve doğru
- Middleware ile URL standardizasyonu yapıldı
- Core Web Vitals hedefleri karşılanıyor
- Lighthouse CI pipeline'a eklendi
- Crawl budget optimize edildi
Sık Sorulan Sorular
CSR uygulamalarını SEO-friendly yapabilir miyim?
Kısmen. Prerendering servisleri (Prerender.io) veya dynamic rendering kullanabilirsiniz. Ama en iyi çözüm SSR/SSG'ye geçmektir. Next.js, Remix veya Astro değerlendirin.
Core Web Vitals sıralamayı ne kadar etkiler?
Önemli ama tek faktör değil. İyi içerik ve backlink'ler hala kritik. Ancak rakipleriniz benzer içerik sunuyorsa, CWV tie-breaker olabilir.
Her sayfayı SSR mı yapmalıyım?
Hayır. SSG mümkünse tercih edin (blog, dokümantasyon). ISR dinamik içerik için idealdir. SSR sadece gerçek zamanlı kişiselleştirme gerektiğinde kullanın.
Googlebot JavaScript framework'lerimi destekliyor mu?
Evet, modern framework'ler destekleniyor. Ama render süresi ve kaynak kullanımı artıyor. SSR/SSG ile rendering yükünü bot'tan almanız önerilir.
Crawl budget küçük siteler için önemli mi?
10.000'den az sayfanız varsa genellikle sorun olmaz. Büyük siteler (100K+ sayfa) için kritik hale gelir.
Sonuç
JavaScript SEO ve performans, modern web geliştirmenin ayrılmaz parçasıdır. Bu yazıda öğrendikleriniz:
- Rendering stratejileri SEO'yu doğrudan etkiler
- Core Web Vitals sıralama faktörüdür
- Crawl budget verimli kullanılmalı
- Edge SEO hız ve standardizasyon sağlar
- CI/CD entegrasyonu sürekli optimizasyon getirir
Bu seri boyunca sayfa içi SEO'nun tüm yönlerini inceledik. Şimdi öğrendiklerinizi uygulamaya koyma zamanı!
Seri Navigasyonu:
- Arama Niyeti ve Semantik SEO
- Teknik Sayfa Yapısı ve Metadata
- İçerik Mimarisi ve Internal Linking
- Schema.org ve JSON-LD
- JavaScript SEO ve Performans (Bu yazı)
Seri Özeti
Bu 5 parçalık seride şunları öğrendiniz:
| Yazı | Ana Konular |
|---|---|
| 1. Arama Niyeti | Search intent, entity SEO, LSI keywords |
| 2. Teknik Yapı | URL, meta tags, canonical, görsel SEO |
| 3. İçerik Mimarisi | Topic clusters, internal linking, content decay |
| 4. Schema Markup | JSON-LD, rich snippets, FAQ/HowTo schema |
| 5. JS SEO | SSR/CSR, Core Web Vitals, crawl budget |
Başarılı SEO, tüm bu unsurların birlikte çalışmasını gerektirir. Tek bir alana odaklanmak yerine, bütüncül bir yaklaşım benimseyin.
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