Next.js Dinamik Routing: SEO Dostu URL Yapısı Oluşturma

Next.js App Router ile dinamik routing kullanarak SEO dostu URL yapısı oluşturmayı öğrenin. Slug parametreleri, catch-all routes ve metadata optimizasyonu.

Next.js, modern web uygulamaları için en popüler React framework'lerinden biridir. App Router ile gelen dinamik routing özellikleri, SEO dostu URL yapıları oluşturmak için güçlü araçlar sunar. Bu rehberde, Next.js ile slug tabanlı URL'ler oluşturmayı ve SEO optimizasyonunu detaylı olarak inceleyeceğiz.

Next.js App Router Temelleri

Next.js 13+ ile gelen App Router, dosya tabanlı routing sistemi kullanır:

app/
├── page.tsx              # / (Ana sayfa)
├── blog/
│   ├── page.tsx          # /blog
│   └── [slug]/
│       └── page.tsx      # /blog/makale-basligi
├── products/
│   └── [category]/
│       └── [id]/
│           └── page.tsx  # /products/electronics/123

Dinamik Route Parametreleri

Tek Parametre [slug]

Blog yazıları gibi tekil içerikler için:

// app/blog/[slug]/page.tsx
import { notFound } from 'next/navigation';

interface PageProps {
  params: { slug: string };
}

export default async function BlogPost({ params }: PageProps) {
  const post = await getPost(params.slug);
  
  if (!post) {
    notFound();
  }
  
  return (
    <article>
      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.content }} />
    </article>
  );
}

Catch-All Routes [...slug]

Çoklu segment URL'ler için:

// app/docs/[...slug]/page.tsx
// Eşleşir: /docs/getting-started
//         /docs/api/reference
//         /docs/guides/advanced/routing

interface PageProps {
  params: { slug: string[] };
}

export default function DocsPage({ params }: PageProps) {
  // params.slug = ['guides', 'advanced', 'routing']
  const path = params.slug.join('/');
  return <div>Docs: {path}</div>;
}

Optional Catch-All [[...slug]]

Root path dahil yakalama:

// app/shop/[[...slug]]/page.tsx
// Eşleşir: /shop
//         /shop/category
//         /shop/category/product

SEO Metadata Optimizasyonu

generateMetadata Fonksiyonu

Dinamik sayfalar için SEO metadata oluşturma:

// app/blog/[slug]/page.tsx
import { Metadata } from 'next';

export async function generateMetadata(
  { params }: PageProps
): Promise<Metadata> {
  const post = await getPost(params.slug);
  
  if (!post) {
    return { title: 'Not Found' };
  }
  
  return {
    title: post.title,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      description: post.excerpt,
      type: 'article',
      publishedTime: post.publishedAt,
      url: `https://example.com/blog/${params.slug}`,
      images: [{
        url: post.featuredImage,
        width: 1200,
        height: 630,
        alt: post.title,
      }],
    },
    twitter: {
      card: 'summary_large_image',
      title: post.title,
      description: post.excerpt,
      images: [post.featuredImage],
    },
  };
}

Static Generation ile SEO

generateStaticParams

Build zamanında statik sayfalar oluşturma:

// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
  const posts = await getAllPosts();
  
  return posts.map((post) => ({
    slug: post.slug,
  }));
}

// Bu fonksiyon build zamanında çalışır ve
// tüm blog yazıları için statik HTML oluşturur

Çoklu Dil Desteği

// app/[locale]/blog/[slug]/page.tsx
export async function generateStaticParams() {
  const locales = ['tr', 'en', 'de'];
  const posts = await getAllPosts();
  
  return locales.flatMap((locale) =>
    posts.map((post) => ({
      locale,
      slug: post.slugs[locale],
    }))
  );
}

Canonical URL Yönetimi

// app/blog/[slug]/page.tsx
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
  const baseUrl = 'https://example.com';
  
  return {
    alternates: {
      canonical: `${baseUrl}/blog/${params.slug}`,
      languages: {
        'en': `${baseUrl}/en/blog/${params.slug}`,
        'tr': `${baseUrl}/tr/blog/${params.slug}`,
        'de': `${baseUrl}/de/blog/${params.slug}`,
      },
    },
  };
}

Slug Oluşturma Fonksiyonu

// lib/utils/slug.ts
export function generateSlug(text: string): string {
  // Türkçe karakter dönüşümü
  const charMap: Record<string, string> = {
    'ş': 's', 'ğ': 'g', 'ü': 'u', 'ö': 'o', 'ç': 'c', 'ı': 'i',
    'Ş': 's', 'Ğ': 'g', 'Ü': 'u', 'Ö': 'o', 'Ç': 'c', 'İ': 'i',
  };
  
  return text
    .toLowerCase()
    .replace(/[şğüöçıŞĞÜÖÇİ]/g, (char) => charMap[char] || char)
    .replace(/[^a-z0-9\s-]/g, '')
    .replace(/\s+/g, '-')
    .replace(/-+/g, '-')
    .replace(/^-|-$/g, '');
}

// Kullanım
const slug = generateSlug('Türkçe Başlık Örneği');
// Çıktı: 'turkce-baslik-ornegi'

Sitemap Oluşturma

// app/sitemap.ts
import { MetadataRoute } from 'next';

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const baseUrl = 'https://example.com';
  const posts = await getAllPosts();
  
  const blogUrls = posts.map((post) => ({
    url: `${baseUrl}/blog/${post.slug}`,
    lastModified: new Date(post.updatedAt),
    changeFrequency: 'weekly' as const,
    priority: 0.8,
  }));
  
  return [
    {
      url: baseUrl,
      lastModified: new Date(),
      changeFrequency: 'daily',
      priority: 1,
    },
    {
      url: `${baseUrl}/blog`,
      lastModified: new Date(),
      changeFrequency: 'daily',
      priority: 0.9,
    },
    ...blogUrls,
  ];
}

Robots.txt Oluşturma

// app/robots.ts
import { MetadataRoute } from 'next';

export default function robots(): MetadataRoute.Robots {
  return {
    rules: {
      userAgent: '*',
      allow: '/',
      disallow: ['/api/', '/admin/', '/private/'],
    },
    sitemap: 'https://example.com/sitemap.xml',
  };
}

Loading ve Error States

Loading UI

// app/blog/[slug]/loading.tsx
export default function Loading() {
  return (
    <div className="animate-pulse">
      <div className="h-8 bg-gray-200 rounded w-3/4 mb-4"></div>
      <div className="h-4 bg-gray-200 rounded w-full mb-2"></div>
      <div className="h-4 bg-gray-200 rounded w-5/6"></div>
    </div>
  );
}

Not Found Sayfası

// app/blog/[slug]/not-found.tsx
export default function NotFound() {
  return (
    <div className="text-center py-20">
      <h1 className="text-4xl font-bold">404</h1>
      <p>İçerik bulunamadı</p>
    </div>
  );
}

Best Practices

Sonuç

Next.js App Router ile dinamik routing, SEO dostu URL yapıları oluşturmak için güçlü ve esnek bir sistem sunar. generateMetadata ve generateStaticParams fonksiyonlarını kullanarak, hem arama motoru optimizasyonu hem de kullanıcı deneyimi açısından mükemmel sonuçlar elde edebilirsiniz. Slug oluşturma fonksiyonlarınızı doğru yapılandırarak ve canonical URL'leri düzgün yöneterek, Next.js projenizin SEO performansını maksimize edebilirsiniz.