Ömer
Özbay

Full-Stack Engineer

LOADING
©2026
Next.js Projesinde SEO'dan Önce Çözdüğüm 7 Kritik Performans Sorunu
MİMARİNext.jsPerformans

Next.js Projesinde SEO'dan Önce Çözdüğüm 7 Kritik Performans Sorunu

calendar_today13 TEM 2026
schedule6 DK OKUMA
boltİLERİ SEVİYE

Next.js Projesinde SEO'dan Önce Çözdüğüm 7 Kritik Performans Sorunu

Pek çok geliştirici SEO'yu organik trafiğin birincil kapısı olarak görür. Meta etiketlere, site haritası yapılarına ve canonical linklere saatler harcarlar — ama alttaki motoru görmezden gelirler: performans.

gucluyumhe.dev inşa ederken öğrendiğim rahatsız edici gerçek şu: SEO'nuz ne kadar mükemmel olursa olsun, Google kötü performans gösteren bir sayfayı sıralamaz. Core Web Vitals doğrudan bir sıralama sinyalidir. Yavaş bir LCP, yüksek bir CLS veya ağır bir TBT skoru, SEO çalışmalarınızı daha başlamadan baltalıyor.

Senior Full Stack Architect olarak, projemdeki bu 7 performans sorununu tek bir SEO etiketine dokunmadan önce çözdüm.


Başlangıç Noktası: 61/100 PageSpeed Skoru

İlk Lighthouse denetimim mobil PageSpeed skoru olarak 61 üzerinden 100 döndürdü. Tanılar spesifik, düzeltilebilir mimari sorunlara işaret ediyordu.

Performans:               61
Erişilebilirlik:          92
En İyi Pratikler:         96
SEO:                      88

Önce performansı düzeltmek beni her kategoride 100'e taşıdı. Sonra SEO kolaydı.


Sorun 1: Render-Blocking Third-Party Script'ler

Ne Oldu?

Sitemin ilk halinde Google Analytics senkron olarak yüklendi. Bu, kritik rendering path sırasında ana iş parçacığını bloke etti ve Total Blocking Time (TBT)'ı artırdı.

Çözüm: @next/third-parties ile Ertelenmiş Script Yükleme

// YANLIS — senkron script ana iş parçacığını bloke eder
<Script src="https://www.googletagmanager.com/gtag/js?id=G-XXXX" />

// DOGRU — yalnızca boşta kalma süresinde yükle
import { GoogleAnalytics } from '@next/third-parties/google';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="tr">
      <body>
        {children}
        <GoogleAnalytics gaId={process.env.NEXT_PUBLIC_GA_ID!} />
      </body>
    </html>
  );
}

Etki: TBT 340ms'den 0ms'ye düştü.


Sorun 2: Optimize Edilmemiş Cover Görselleri LCP'yi Mahvediyor

Ne Oldu?

Blog yazısı kapak görselleri Unsplash'tan ham <img> etiketleri olarak yükleniyordu. Mobilde, başka hiçbir şey render edilmeden önce 2MB'lık bir JPEG indiriliyor ve Largest Contentful Paint (LCP) skorum mahvediliyordu.

Çözüm: priority ile Next.js Image Bileşeni

import Image from 'next/image';

// Hero görseli: katlama üstü görseller için her zaman priority kullan
<Image
  src={post.coverImage}
  alt={post.title}
  width={1200}
  height={630}
  priority={true}
  sizes="(max-width: 768px) 100vw, 1200px"
  style={{ objectFit: 'cover' }}
/>

// Katlama altı görseller: varsayılan olarak lazy load
<Image
  src={relatedPost.coverImage}
  alt={relatedPost.title}
  width={400}
  height={210}
  loading="lazy"
/>

Etki: LCP mobilde 4.2 saniyeden 1.1 saniyeye iyileşti.


Sorun 3: Gereksiz JavaScript Gönderen Client Bileşenler

Ne Oldu?

Sadece küçük bir alt bileşenin useState'e ihtiyaç duyduğu için tüm sayfa bölümlerini 'use client' direktifleriyle sardım. Bu, Next.js'in tüm React render ağacını tarayıcıya JavaScript olarak göndermesine neden oldu.

Çözüm: use client'ı Yaprak Düğüme İt

// YANLIS — tüm bölüm bir client bundle'a dönüşür
'use client';
export default function BlogSection() {
  const [liked, setLiked] = useState(false);
  return (
    <section>
      <h2>Son Yazılar</h2>
      <PostList posts={posts} />
      <LikeButton liked={liked} onLike={() => setLiked(true)} />
    </section>
  );
}

// DOGRU — server component minimal bir client component sarar
// BlogSection.tsx (Server Component — sıfır JavaScript gönderir)
export default function BlogSection({ posts }: Props) {
  return (
    <section>
      <h2>Son Yazılar</h2>
      <PostList posts={posts} />
      <LikeButton />
    </section>
  );
}

// LikeButton.tsx (Client Component — minimal JavaScript)
'use client';
export function LikeButton() {
  const [liked, setLiked] = useState(false);
  return <button onClick={() => setLiked(true)}>{liked ? 'Beğenildi' : 'Beğen'}</button>;
}

Etki: JavaScript bundle yaklaşık 67kb azaldı.


Sorun 4: Dinamik Yüklenen Fontlardan Kaynaklanan Layout Shift

Ne Oldu?

Google Fonts asenkron olarak yüklendi. İlk boyamada geri dönüş fontları render edildi, ardından özel font yüklendi — metin yeniden aktıkça büyük bir Cumulative Layout Shift (CLS) oluştu.

Çözüm: display swap ve Boyut Ön Yüklemeli next/font

// app/layout.tsx
import { Inter, JetBrains_Mono } from 'next/font/google';

const inter = Inter({
  subsets: ['latin'],
  display: 'swap',
  preload: true,
  variable: '--font-inter',
});

const jetbrainsMono = JetBrains_Mono({
  subsets: ['latin'],
  display: 'swap',
  variable: '--font-mono',
  weight: ['400', '700'],
});

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="tr" className={`${inter.variable} ${jetbrainsMono.variable}`}>
      <body>{children}</body>
    </html>
  );
}

Etki: CLS 0.18'den 0.00'a düştü.


Sorun 5: Statik Sayfaların Pre-Render Edilmemesi

Ne Oldu?

Blog yazısı sayfalarım, içerik yalnızca deploy'da değişen markdown dosyaları olmasına rağmen her ziyarette istek anında fetch() kullanıyordu. Bu, her sayfa yüklemesini gerekenden yavaş yapıyordu.

Çözüm: generateStaticParams ve Statik Markdown Ayrıştırma

// app/blog/[slug]/page.tsx

// Tüm blog yazısı rotalarını build anında önceden oluştur
export async function generateStaticParams(): Promise<{ slug: string }[]> {
  const posts = await getAllPostSlugs();
  return posts.map((slug) => ({ slug }));
}

// Bu sayfa artık statik HTML dosyası olarak sunuluyor — sıfır sunucu yükü
export default async function BlogPostPage({ params }: { params: { slug: string } }) {
  const post = await getPostBySlug(params.slug);

  if (!post) notFound();

  return <ArticleLayout post={post} />;
}

Etki: Time to First Byte (TTFB) 320ms'den 18ms'ye düştü (CDN edge'den sunuldu).


Sorun 6: Harici Origin'ler için Eksik rel="preconnect"

Ne Oldu?

Tarayıcı, render anında Unsplash ve Google Fonts'a soğuk TCP/TLS bağlantıları kuruyordu. Her bağlantı, tek bir bayt içerik gelmeden önce 150–300ms ağ gecikmesi ekliyordu.

Çözüm: Document Head'de Preconnect İpuçları

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="tr">
      <head>
        <link rel="preconnect" href="https://images.unsplash.com" />
        <link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
        <link rel="dns-prefetch" href="https://www.google-analytics.com" />
      </head>
      <body>{children}</body>
    </html>
  );
}

Etki: Harici kaynak yükleme süresi ortalama yaklaşık 240ms azaldı.


Sorun 7: API Route'ları için Önbellek Stratejisi Yokluğu

Ne Oldu?

Tüm markdown dosyalarını her istekte okuyan ve ayrıştıran bir /api/posts route'um vardı. Birden fazla ziyaretçi aynı anda sayfaya girdiğinde, bu her çağrıda gereksiz dosya okuma işlemine neden oluyordu.

Çözüm: Revalidation ile In-Memory Cache

// lib/posts-cache.ts
interface CacheEntry<T> {
  data: T;
  cachedAt: number;
}

const CACHE_TTL_MS = 60 * 60 * 1000; // 1 saat
const cache = new Map<string, CacheEntry<unknown>>();

export async function getCached<T>(
  key: string,
  fetcher: () => Promise<T>
): Promise<T> {
  const entry = cache.get(key) as CacheEntry<T> | undefined;
  const now = Date.now();

  if (entry && now - entry.cachedAt < CACHE_TTL_MS) {
    return entry.data;
  }

  const data = await fetcher();
  cache.set(key, { data, cachedAt: now });
  return data;
}

// API route'da kullanım
export async function GET() {
  try {
    const posts = await getCached('all-posts', () => getAllPosts());
    return Response.json(posts);
  } catch (error) {
    console.error('Yazılar alınamadı:', error);
    return Response.json({ error: 'Internal Server Error' }, { status: 500 });
  }
}

Etki: API yanıt süresi cache hit'lerinde yaklaşık 180ms'den 3ms'ye düştü.


Nihai Skor: Her Kategoride 100

7 sorunu düzelttikten sonra Lighthouse denetim sonuçları:

Performans:               100
Erişilebilirlik:          100
En İyi Pratikler:         100
SEO:                      100

SEO çalışmaları bu noktada gerçekten önem kazanmaya başlıyor. Performansı düzelttikten sonra eklenen tüm yapılandırılmış veriler, site haritaları ve Open Graph etiketleri, teknik temel sağlam olduğu için tamamen indekslendi.


Temel İçgörü

Performans SEO'dur. Core Web Vitals — LCP, CLS, TBT — Google sıralama sinyalleridir. Mükemmel bir site haritası ve zengin schema markup'a sahip ama 60/100 PageSpeed skoru olan bir site, iyi performans gösteren bir rakibi hiçbir zaman geçemez.

Herhangi bir Next.js projesi için doğru operasyon sırası:

1. Rendering mimarisini düzelt (RSC vs Client Components)
2. Görselleri optimize et (next/image, priority, sizes)
3. Layout shift'leri ortadan kaldır (next/font, ayrılmış boyutlar)
4. Kritik olmayan script'leri ertele (third-parties, analytics)
5. Statik içeriği pre-render et (generateStaticParams, ISR)
6. Harici origin'ler için preconnect ipuçları ekle
7. Dinamik veriler için önbellek uygula
8. SEO metadata, structured data ve site haritaları ekle

Bu sırayı takip edin ve SEO çalışmalarınız sıralanmak için ihtiyaç duydukları performans temeline sahip olacak.

Ömer Özbay
YAZAN

Ömer Özbay

Yüksek performanslı arka yüz mimarilerini piksel hassasiyetinde ön yüz deneyimleriyle birleştirmede uzmanlaşmış Tam Yığın Geliştirici. Yapay zeka ve modern web teknolojileriyle geleceği inşa ediyor.

Next.js Projesinde SEO'dan Önce Çözdüğüm 7 Kritik Performans Sorunu | Ömer Özbay Günlük | Ömer Özbay