Skip to content

Kapitel 7: Datenabfrage

📖 Lernziele

In diesem Kapitel lernen Sie:

  • ✅ Daten auf Server Components abfragen (async/await)
  • ✅ Caching-Strategien verstehen
  • ✅ Daten auf Client Components abfragen (useEffect, SWR)
  • ✅ API-Anfragen kapseln (Request Wrapper)
  • ✅ CORS-Probleme lösen
  • ✅ Statische Generierung (SSG), SSR und ISR verstehen

7.1 Server-Side Datenabfrage (Empfohlen)

🎯 Datenabfrage in Server Components

Vorteile:

  • ✅ Keine API-Schicht nötig (direkter DB-Zugriff möglich)
  • ✅ Bessere Performance (weniger Client-JS)
  • ✅ Bessere SEO (Daten sind im HTML)

📝 Beispiel: Einfache Datenabfrage

Datei: app/blog/page.js (Server Component)

jsx
// Asynchrone Server Component
async function getPosts() {
  const res = await fetch('https://api.example.com/posts');
  if (!res.ok) throw new Error('Fehler beim Laden');
  return res.json();
}

export default async function BlogPage() {
  const posts = await getPosts();
  
  return (
    <main>
      <h1>Blog-Artikel</h1>
      <ul>
        {posts.map(post => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </main>
  );
}

📦 Caching-Strategien

Next.js bietet verschiedene Caching-Optionen:

OptionBeschreibungVerwendung
StandardDaten werden gecached (Standard)fetch(url)
Kein CacheDaten immer neu abfragenfetch(url, { cache: 'no-store' })
RevalidierungCache nach X Sekunden erneuernfetch(url, { next: { revalidate: 60 } })

Beispiele:

javascript
// 1. Standard (gecacht)
const res = await fetch('https://api.example.com/posts');

// 2. Kein Cache (immer aktuell)
const res = await fetch('https://api.example.com/posts', { cache: 'no-store' });

// 3. Revalidierung (alle 60 Sekunden)
const res = await fetch('https://api.example.com/posts', { 
  next: { revalidate: 60 } 
});

7.2 Client-Side Datenabfrage

🎯 Wann Client-Side?

  • ✅ Bei interaktiven Komponenten (die Hooks verwenden)
  • ✅ Wenn Daten erst nach Benutzerinteraktion geladen werden sollen
  • ✅ Für private Daten (die nicht SEO-relevant sind)

📝 Methode 1: useEffect + fetch

Datei: components/PostList.js (Client Component)

jsx
'use client';

import { useState, useEffect } from 'react';

export default function PostList() {
  const [posts, setPosts] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    async function loadPosts() {
      try {
        const res = await fetch('https://api.example.com/posts');
        if (!res.ok) throw new Error('Fehler');
        const data = await res.json();
        setPosts(data);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    }
    
    loadPosts();
  }, []);
  
  if (loading) return <p>Lädt...</p>;
  if (error) return <p>Fehler: {error}</p>;
  
  return (
    <ul>
      {posts.map(post => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

📝 Methode 2: SWR (Empfohlen)

Installation:

bash
pnpm add swr

Verwendung:

jsx
'use client';

import useSWR from 'swr';

const fetcher = (url) => fetch(url).then(res => res.json());

export default function PostList() {
  const { data, error, isLoading } = useSWR('https://api.example.com/posts', fetcher);
  
  if (isLoading) return <p>Lädt...</p>;
  if (error) return <p>Fehler: {error.message}</p>;
  
  return (
    <ul>
      {data.map(post => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

Vorteile von SWR:

  • ✅ Automatisches Caching
  • ✅ Automatisches Revalidierung (wenn Fokus zurückkehrt)
  • ✅ Fehlerbehandlung integriert

7.3 API-Anfragen kapseln

🎯 Warum kapseln?

  • ✅ Einheitliche Fehlerbehandlung
  • ✅ Basis-URL zentral verwalten
  • ✅ Token/Auth-Header automatisch hinzufügen

📝 Beispiel: API-Client erstellen

Datei: lib/api.js

javascript
const BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'https://api.example.com';

async function request(endpoint, options = {}) {
  const url = `${BASE_URL}${endpoint}`;
  
  const defaultOptions = {
    headers: {
      'Content-Type': 'application/json',
      // Token hinzufügen (falls vorhanden)
      ...(localStorage.getItem('token') && {
        'Authorization': `Bearer ${localStorage.getItem('token')}`
      }),
    },
  };
  
  const res = await fetch(url, { ...defaultOptions, ...options });
  
  if (!res.ok) {
    const error = await res.json();
    throw new Error(error.message || 'API-Fehler');
  }
  
  return res.json();
}

export const api = {
  get: (endpoint) => request(endpoint),
  post: (endpoint, data) => request(endpoint, {
    method: 'POST',
    body: JSON.stringify(data),
  }),
  put: (endpoint, data) => request(endpoint, {
    method: 'PUT',
    body: JSON.stringify(data),
  }),
  delete: (endpoint) => request(endpoint, { method: 'DELETE' }),
};

📝 Verwendung

jsx
import { api } from '@/lib/api';

// In einer Server Component
const posts = await api.get('/posts');

// In einer Client Component
useEffect(() => {
  api.get('/posts').then(setPosts);
}, []);

7.4 CORS-Probleme lösen

🎯 Was ist CORS?

CORS (Cross-Origin Resource Sharing) verhindert Anfragen an eine andere Domain aus Sicherheitsgründen.

🛠️ Lösung 1: Next.js Rewrite (Proxy)

Datei: next.config.js

javascript
/** @type {import('next').NextConfig} */
const nextConfig = {
  async rewrites() {
    return [
      {
        source: '/api/:path*',
        destination: 'https://api.example.com/:path*',
      },
    ];
  },
};

module.exports = nextConfig;

Verwendung:

javascript
// Anfrage an /api/posts wird zu https://api.example.com/posts weitergeleitet
const res = await fetch('/api/posts');

🛠️ Lösung 2: API Route als Proxy

Datei: app/api/proxy/route.js

javascript
export async function GET(request) {
  const url = new URL(request.url);
  const endpoint = url.searchParams.get('endpoint');
  
  const res = await fetch(`https://api.example.com/${endpoint}`, {
    headers: {
      'Authorization': `Bearer ${process.env.API_TOKEN}`,
    },
  });
  
  const data = await res.json();
  return Response.json(data);
}

7.5 Rendering-Modi (SSG, SSR, ISR)

🎯 Drei Rendering-Modi

ModusBeschreibungVerwendung
SSG (Static Site Generation)HTML wird zur Build-Zeit generiertBlog, Dokumentation
SSR (Server-Side Rendering)HTML wird bei jeder Anfrage generiertE-Commerce, Suche
ISR (Incremental Static Regeneration)SSG + automatische AktualisierungBlog mit regelmäßigen Updates

📝 SSG (Static Site Generation)

Standardverhalten (fetch gecacht):

jsx
export default async function Page() {
  // Wird zur Build-Zeit ausgeführt (SSG)
  const res = await fetch('https://api.example.com/posts');
  const posts = await res.json();
  
  return <PostList posts={posts} />;
}

📝 SSR (Server-Side Rendering)

Kein Cache (jede Anfrage neu):

jsx
export default async function Page() {
  // Wird bei jeder Anfrage ausgeführt (SSR)
  const res = await fetch('https://api.example.com/posts', { cache: 'no-store' });
  const posts = await res.json();
  
  return <PostList posts={posts} />;
}

📝 ISR (Incremental Static Regeneration)

Revalidierung nach X Sekunden:

jsx
export default async function Page() {
  // SSG, aber alle 60 Sekunden aktualisieren
  const res = await fetch('https://api.example.com/posts', { 
    next: { revalidate: 60 } 
  });
  const posts = await res.json();
  
  return <PostList posts={posts} />;
}

📝 Dynamische Routen mit generateStaticParams

Datei: app/blog/[slug]/page.js

jsx
// Statische Generierung für dynamische Routen
export async function generateStaticParams() {
  const posts = await fetch('https://api.example.com/posts').then(res => res.json());
  
  return posts.map(post => ({
    slug: post.slug,
  }));
}

export default async function BlogPost({ params }) {
  const post = await fetch(`https://api.example.com/posts/${params.slug}`).then(res => res.json());
  
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </article>
  );
}

📝 Zusammenfassung

In diesem Kapitel haben Sie gelernt:

KonzeptErklärung
Server-SideDatenabfrage in Server Components (async/await)
Client-SideuseEffect + fetch oder SWR
API-KapselungEinheitliche Fehlerbehandlung, Basis-URL
CORSLösung über Rewrites oder API-Routen
SSG/SSR/ISRVerschiedene Rendering-Modi für verschiedene Anwendungsfälle

✅ Nächste Schritte

  1. Übung: Erstellen Sie eine Server Component, die Daten von einer API lädt
  2. Übung: Erstellen Sie eine Client Component mit SWR
  3. Weiter geht's: Kapitel 8 - State Management

🎯 Selbsttest

Frage 1: Was ist der Vorteil von Server-Side Datenabfrage?

Antwort anzeigen Bessere SEO, da Daten im HTML enthalten sind; bessere Performance, da weniger Client-JS benötigt wird.

Frage 2: Was ist der Unterschied zwischen SSG und SSR?

Antwort anzeigen SSG generiert HTML zur Build-Zeit (schneller), SSR generiert HTML bei jeder Anfrage (immer aktuell).

Frage 3: Wie löst man CORS-Probleme in Next.js?

Antwort anzeigen Über Rewrites in `next.config.js` oder durch Verwendung von API-Routen als Proxy.

🚀 Weiter zu Kapitel 8: State Management

Frei für alle Anfänger