İçeriğe geç
nextjs

Next.js Error Handling: Hata Yönetimi Rehberi

Tarık Tunç
Next.js Error Handling: Hata Yönetimi Rehberi

Next.js Error Handling: Hata Yönetimi Rehberi

Hata yönetimi, güvenilir web uygulamalarının vazgeçilmez bir parçasıdır. Next.js App Router, dosya tabanlı hata yakalama mekanizmasıyla her route segmenti için otomatik error boundary'ler oluşturmanıza olanak tanır. Bu rehberde error.tsx, not-found.tsx, global-error.tsx dosyalarını ve Server/Client Component'lerde hata yönetimini detaylı inceleyeceğiz.

error.tsx ile Hata Yakalama

error.tsx dosyası, bir route segmentindeki hataları otomatik olarak yakalar ve bir fallback UI gösterir:

// app/dashboard/error.tsx
'use client'; // Error boundary'ler Client Component olmalı

import { useEffect } from 'react';

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  useEffect(() => {
    // Hata loglama servisi
    console.error('Dashboard hatası:', error);
  }, [error]);

  return (
    <div className="flex flex-col items-center justify-center min-h-[400px] gap-4">
      <h2 className="text-2xl font-bold text-red-600">
        Bir hata oluştu!
      </h2>
      <p className="text-gray-600">
        {error.message || 'Beklenmeyen bir hata meydana geldi.'}
      </p>
      <button
        onClick={() => reset()}
        className="px-6 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
      >
        Tekrar Dene
      </button>
    </div>
  );
}
İpucu: reset() fonksiyonu, hatalı segmenti yeniden render etmeye çalışır. Bu, geçici hatalar (ağ kesintisi gibi) için kullanıcıya kolay bir kurtarma yolu sunar.

not-found.tsx ile 404 Sayfaları

// app/not-found.tsx (Global 404)
import Link from 'next/link';

export default function NotFound() {
  return (
    <div className="flex flex-col items-center justify-center min-h-screen">
      <h1 className="text-6xl font-bold text-gray-300">404</h1>
      <h2 className="text-2xl font-semibold mt-4">Sayfa Bulunamadı</h2>
      <p className="text-gray-600 mt-2">
        Aradığınız sayfa mevcut değil veya taşınmış olabilir.
      </p>
      <Link
        href="/"
        className="mt-6 px-6 py-3 bg-blue-500 text-white rounded hover:bg-blue-600"
      >
        Ana Sayfaya Dön
      </Link>
    </div>
  );
}

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

export default async function BlogPost({ params }: { params: { slug: string } }) {
  const post = await getPost(params.slug);

  if (!post) {
    notFound(); // En yakın not-found.tsx'i render eder
  }

  return <article>{post.title}</article>;
}

Global Error Handler

// app/global-error.tsx
'use client';

export default function GlobalError({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  return (
    <html>
      <body>
        <div className="flex flex-col items-center justify-center min-h-screen">
          <h1 className="text-3xl font-bold text-red-600">
            Kritik Hata
          </h1>
          <p className="mt-4 text-gray-600">
            Uygulamada kritik bir hata oluştu.
          </p>
          <button
            onClick={() => reset()}
            className="mt-6 px-6 py-3 bg-red-500 text-white rounded"
          >
            Uygulamayı Yenile
          </button>
        </div>
      </body>
    </html>
  );
}
Uyarı: global-error.tsx sadece production'da aktiftir. Development'ta Next.js error overlay gösterilir. Ayrıca global-error.tsx kendi <html> ve <body> etiketlerini tanımlamalıdır.

Server Component'lerde Hata Yönetimi

// Veri çekme fonksiyonunda hata yönetimi
async function getProducts() {
  try {
    const res = await fetch('https://api.example.com/products', {
      next: { revalidate: 3600 },
    });

    if (!res.ok) {
      throw new Error(`API hatası: ${res.status} ${res.statusText}`);
    }

    return res.json();
  } catch (error) {
    if (error instanceof TypeError) {
      throw new Error('Ağ bağlantısı hatası. Lütfen tekrar deneyin.');
    }
    throw error;
  }
}

// Graceful degradation
export default async function ProductsPage() {
  let products = [];
  let error = null;

  try {
    products = await getProducts();
  } catch (e) {
    error = e instanceof Error ? e.message : 'Bilinmeyen hata';
  }

  if (error) {
    return (
      <div className="bg-yellow-50 border border-yellow-200 p-4 rounded">
        <p>Ürünler yüklenemedi: {error}</p>
        <p>Lütfen daha sonra tekrar deneyin.</p>
      </div>
    );
  }

  return <ProductGrid products={products} />;
}

API Route'larda Hata Yönetimi

// app/api/posts/route.ts
import { NextResponse } from 'next/server';
import { ZodError, z } from 'zod';

const postSchema = z.object({
  title: z.string().min(3, 'Başlık en az 3 karakter olmalı'),
  content: z.string().min(10, 'İçerik en az 10 karakter olmalı'),
  tags: z.array(z.string()).optional(),
});

export async function POST(request: Request) {
  try {
    const body = await request.json();
    const validated = postSchema.parse(body);

    const post = await prisma.post.create({ data: validated });
    return NextResponse.json(post, { status: 201 });
  } catch (error) {
    if (error instanceof ZodError) {
      return NextResponse.json(
        {
          error: 'Validasyon hatası',
          details: error.errors.map((e) => ({
            field: e.path.join('.'),
            message: e.message,
          })),
        },
        { status: 400 }
      );
    }

    if (error instanceof SyntaxError) {
      return NextResponse.json(
        { error: 'Geçersiz JSON formatı' },
        { status: 400 }
      );
    }

    console.error('Sunucu hatası:', error);
    return NextResponse.json(
      { error: 'Sunucu hatası' },
      { status: 500 }
    );
  }
}

Error Boundary Hiyerarşisi

app/
├── error.tsx              # Tüm sayfalar için fallback
├── not-found.tsx          # Global 404
├── global-error.tsx       # Root layout hataları
├── dashboard/
│   ├── error.tsx          # Dashboard hataları
│   ├── page.tsx
│   └── settings/
│       ├── error.tsx      # Settings'e özel hatalar
│       └── page.tsx
└── blog/
    ├── error.tsx          # Blog hataları
    ├── not-found.tsx      # Blog 404
    └── [slug]/
        ├── error.tsx      # Tekil yazı hataları
        └── page.tsx
Bilgi: Hata bir error.tsx tarafından yakalanamadığında, bir üst segmentteki error.tsx'e yükseltilir. Bu, DOM'daki event bubbling mekanizmasına benzer.

Hata Loglama Entegrasyonu

// lib/error-logger.ts
export async function logError(error: Error, context?: Record<string, any>) {
  // Sentry, LogRocket veya benzeri servise gönder
  if (process.env.NODE_ENV === 'production') {
    await fetch('https://sentry.io/api/capture', {
      method: 'POST',
      body: JSON.stringify({
        message: error.message,
        stack: error.stack,
        context,
        timestamp: new Date().toISOString(),
      }),
    });
  } else {
    console.error('[DEV ERROR]', error, context);
  }
}

// error.tsx'te kullanım
'use client';

import { useEffect } from 'react';
import { logError } from '@/lib/error-logger';

export default function Error({ error, reset }: { error: Error; reset: () => void }) {
  useEffect(() => {
    logError(error, { page: 'dashboard' });
  }, [error]);

  return <div>Hata oluştu. <button onClick={reset}>Tekrar Dene</button></div>;
}

Sonuç

Next.js'in dosya tabanlı error handling sistemi, hata yönetimini organize ve öngörülebilir hale getirir. error.tsx ile route bazlı hata yakalama, not-found.tsx ile 404 sayfaları ve global-error.tsx ile kritik hatalar ayrı ayrı yönetilir. Bu mekanizmaları hata loglama servisleriyle birleştirerek kullanıcı deneyimini korurken, sorunları hızlıca tespit edip çözebilirsiniz.

Kaynaklar

İlgili Yazılar