Appearance
Kapitel 12: Praxisprojekt - E-Commerce (Fortgeschritten)
📖 Lernziele
In diesem Kapitel lernen Sie:
- ✅ Eine E-Commerce-Startseite mit Next.js 14 erstellen
- ✅ Layout aufteilen (Header, Sidebar, Inhalt)
- ✅ Hauptfunktionen implementieren (Slider, Produktliste, Warenkorb)
- ✅ State Management mit Zustand (Warenkorb)
- ✅ Datenabfrage (SSR für Produkte, Client für Warenkorb)
- ✅ Performance-Optimierung (Bild-Lazy-Loading, Code-Splitting)
12.1 Projektarchitektur
🎯 Projektziel
Eine E-Commerce-Startseite mit folgenden Funktionen:
- ✅ Startseite mit Slider und Produktliste
- ✅ Produktdetailseite
- ✅ Warenkorb (Hinzufügen, Entfernen, Aktualisieren)
- ✅ Zustandsverwaltung mit Zustand
- ✅ Responsive Design
📂 Verzeichnisstruktur
app/
├── layout.js # Globales Layout
├── page.js # Startseite (/)
├── produkte/
│ ├── page.js # Produktliste (/produkte)
│ └── [id]/
│ └── page.js # Produktdetail (/produkte/[id])
├── warenkorb/
│ └── page.js # Warenkorb-Seite (/warenkorb)
└── components/ # Wiederverwendbare Komponenten
├── Header.js
├── Slider.js
├── ProduktKarte.js
└── WarenkorbIcon.js12.2 Layout & Komponenten aufbauen
🎨 Globales Layout (app/layout.js)
jsx
import './globals.css';
import Header from '@/components/Header';
import { WarenkorbProvider } from '@/context/WarenkorbContext';
export const metadata = {
title: 'Mein E-Commerce Shop | Next.js 14',
description: 'Eine moderne E-Commerce-Webseite mit Next.js 14',
};
export default function RootLayout({ children }) {
return (
<html lang="de">
<body>
<WarenkorbProvider>
<Header />
<main className="container mx-auto px-4 py-8">
{children}
</main>
<footer className="bg-gray-800 text-white py-8">
<div className="container mx-auto px-4 text-center">
<p>© 2024 Mein E-Commerce Shop. Alle Rechte vorbehalten.</p>
</div>
</footer>
</WarenkorbProvider>
</body>
</html>
);
}🏠 Header-Komponente (components/Header.js)
jsx
'use client';
import Link from 'next/link';
import WarenkorbIcon from './WarenkorbIcon';
export default function Header() {
return (
<header className="bg-white shadow">
<nav className="container mx-auto px-4 py-6">
<div className="flex flex-col md:flex-row justify-between items-center">
<Link href="/" className="text-3xl font-bold text-gray-800 mb-4 md:mb-0">
Mein Shop
</Link>
<div className="flex items-center space-x-8">
<Link href="/produkte" className="text-gray-600 hover:text-gray-900">
Produkte
</Link>
<Link href="/warenkorb" className="relative">
<WarenkorbIcon />
</Link>
</div>
</div>
</nav>
</header>
);
}12.3 Hauptfunktionen implementieren
🖼️ Slider-Komponente (components/Slider.js)
jsx
'use client';
import { useState, useEffect } from 'react';
const slides = [
{ id: 1, image: '/slider1.jpg', title: 'Sonderangebot', subtitle: 'Bis zu 50% Rabatt' },
{ id: 2, image: '/slider2.jpg', title: 'Neue Kollektion', subtitle: 'Entdecken Sie die neuesten Trends' },
{ id: 3, image: '/slider3.jpg', title: 'Kostenloser Versand', subtitle: 'Für Bestellungen über 50€' },
];
export default function Slider() {
const [currentSlide, setCurrentSlide] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setCurrentSlide((prev) => (prev + 1) % slides.length);
}, 5000);
return () => clearInterval(timer);
}, []);
return (
<div className="relative h-96 overflow-hidden rounded-lg mb-12">
{slides.map((slide, index) => (
<div
key={slide.id}
className={`absolute inset-0 transition-opacity duration-1000 ${
index === currentSlide ? 'opacity-100' : 'opacity-0'
}`}
>
<img src={slide.image} alt={slide.title} className="w-full h-full object-cover" />
<div className="absolute inset-0 bg-black bg-opacity-40 flex items-center justify-center">
<div className="text-center text-white">
<h2 className="text-5xl font-bold mb-4">{slide.title}</h2>
<p className="text-2xl">{slide.subtitle}</p>
</div>
</div>
</div>
))}
<div className="absolute bottom-4 left-1/2 transform -translate-x-1/2 flex space-x-2">
{slides.map((_, index) => (
<button
key={index}
onClick={() => setCurrentSlide(index)}
className={`w-3 h-3 rounded-full ${index === currentSlide ? 'bg-white' : 'bg-white bg-opacity-50'}`}
/>
))}
</div>
</div>
);
}🛍️ Produktkarte (components/ProduktKarte.js)
jsx
import Image from 'next/image';
import { useWarenkorb } from '@/context/WarenkorbContext';
export default function ProduktKarte({ produkt }) {
const { addToWarenkorb } = useWarenkorb();
return (
<div className="border rounded-lg overflow-hidden shadow hover:shadow-lg transition">
<img src={produkt.bild} alt={produkt.name} className="w-full h-48 object-cover" />
<div className="p-6">
<h3 className="text-xl font-semibold mb-2">{produkt.name}</h3>
<p className="text-gray-600 mb-4">{produkt.beschreibung}</p>
<div className="flex justify-between items-center">
<span className="text-2xl font-bold text-blue-600">{produkt.preis.toFixed(2)} €</span>
<button
onClick={() => addToWarenkorb(produkt)}
className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700"
>
In den Warenkorb
</button>
</div>
</div>
</div>
);
}12.4 State Management mit Zustand
🏪 Warenkorb-Kontext (context/WarenkorbContext.js)
javascript
'use client';
import { createContext, useContext, useState } from 'react';
const WarenkorbContext = createContext();
export function WarenkorbProvider({ children }) {
const [warenkorb, setWarenkorb] = useState([]);
const addToWarenkorb = (produkt) => {
setWarenkorb((prev) => {
const existiert = prev.find((item) => item.id === produkt.id);
if (existiert) {
return prev.map((item) =>
item.id === produkt.id ? { ...item, menge: item.menge + 1 } : item
);
}
return [...prev, { ...produkt, menge: 1 }];
});
};
const removeFromWarenkorb = (produktId) => {
setWarenkorb((prev) => prev.filter((item) => item.id !== produktId));
};
const updateMenge = (produktId, menge) => {
if (menge < 1) return;
setWarenkorb((prev) =>
prev.map((item) => (item.id === produktId ? { ...item, menge } : item))
);
};
const getGesamtpreis = () => {
return warenkorb.reduce((sum, item) => sum + item.preis * item.menge, 0);
};
return (
<WarenkorbContext.Provider
value={{
warenkorb,
addToWarenkorb,
removeFromWarenkorb,
updateMenge,
getGesamtpreis,
}}
>
{children}
</WarenkorbContext.Provider>
);
}
export function useWarenkorb() {
const context = useContext(WarenkorbContext);
if (!context) {
throw new Error('useWarenkorb muss innerhalb von WarenkorbProvider verwendet werden');
}
return context;
}12.5 Datenabfrage
📦 Produktdaten (lib/produkte.js)
javascript
export const produkte = [
{
id: 1,
name: 'Smartphone XYZ',
beschreibung: 'Hochleistungs-Smartphone mit toller Kamera',
preis: 599.99,
bild: '/produkt1.jpg',
kategorie: 'Elektronik',
},
{
id: 2,
name: 'Laptop ABC',
beschreibung: 'Leichter und leistungsstarker Laptop',
preis: 1299.99,
bild: '/produkt2.jpg',
kategorie: 'Elektronik',
},
// Weitere Produkte...
];
export function getProdukte() {
return produkte;
}
export function getProduktById(id) {
return produkte.find((p) => p.id === parseInt(id));
}🏠 Startseite (app/page.js) - Server Component
jsx
import Slider from '@/components/Slider';
import ProduktKarte from '@/components/ProduktKarte';
import { getProdukte } from '@/lib/produkte';
export default function HomePage() {
const produkte = getProdukte();
return (
<div>
<Slider />
<section className="mb-12">
<h2 className="text-3xl font-bold mb-8">Beliebte Produkte</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
{produkte.slice(0, 8).map((produkt) => (
<ProduktKarte key={produkt.id} produkt={produkt} />
))}
</div>
</section>
</div>
);
}12.6 Performance-Optimierung
⚡ Bild-Lazy-Loading
jsx
import Image from 'next/image';
<Image
src={produkt.bild}
alt={produkt.name}
width={500}
height={300}
loading="lazy" // Lazy Loading
className="w-full h-48 object-cover"
/>📦 Code-Splitting (Dynamische Importe)
jsx
import dynamic from 'next/dynamic';
const HeavyComponent = dynamic(() => import('@/components/HeavyComponent'), {
loading: () => <p>Lädt...</p>,
ssr: false, // Nur Client-seitig
});📝 Zusammenfassung
In diesem Kapitel haben Sie gelernt:
| Konzept | Erklärung |
|---|---|
| Projektarchitektur | Startseite, Produktliste, Warenkorb |
| Komponenten | Header, Slider, Produktkarte |
| State Management | Zustand für Warenkorb |
| Datenabfrage | Server Components für Produkte |
| Performance | Lazy Loading, Code-Splitting |
✅ Nächste Schritte
- ✅ Übung: Fügen Sie eine Kategorieseite hinzu
- ✅ Übung: Implementieren Sie eine Suche
- ✅ Weiter geht's: Kapitel 13 - Erweiterte Konfiguration & Optimierung
🎯 Selbsttest
Frage 1: Welches Werkzeug wird für State Management in diesem Projekt verwendet?
Antwort anzeigen
Zustand (über Context API)Frage 2: Was ist der Vorteil von Server Components bei der Datenabfrage?
Antwort anzeigen
Bessere SEO, schnellere Ladezeiten, da Daten auf dem Server abgerufen werden.Frage 3: Wie optimiert man Bilder in Next.js?
Antwort anzeigen
Verwenden Sie die `next/image` Komponente mit `loading="lazy"` für Lazy Loading.🚀 Weiter zu Kapitel 13: Erweiterte Konfiguration & Optimierung
