Spreewaldzeit — MVP
Private Ferienwohnungs-Website mit zwei Wohnungen. Ruhig, editorial, wartungsarm. Technik-Stack bewusst schlank gehalten — kein Hotel-PMS-Overhead, aber vorbereitet für spätere Erweiterungen (iCal-Sync, Direktbuchung, Zahlungsabwicklung).
Tech-Stack
- Next.js 14 (App Router, Server Components)
- TypeScript, strict
- Prisma + SQLite (einfach portierbar nach Postgres/MySQL)
- Tailwind CSS mit eigener Naturpalette
- React Hook Form + Zod für Formulare & Validierung
- jose für signierte Session-Cookies (kein NextAuth-Overhead)
- nodemailer für Mailversand (mit Dev-Fallback ins Terminal)
- react-day-picker für den Verfügbarkeitskalender
Projektstruktur
spreewaldzeit/
├── app/
│ ├── layout.tsx # Root-Layout + Google Fonts
│ ├── globals.css # Tailwind-Layer + globale Styles
│ ├── page.tsx # Startseite
│ ├── not-found.tsx
│ ├── wohnungen/[slug]/
│ │ └── page.tsx # Wohnungsdetail
│ ├── anfrage/
│ │ └── page.tsx # Anfrageformular
│ ├── datenschutz/page.tsx
│ ├── impressum/page.tsx
│ ├── admin/
│ │ ├── layout.tsx
│ │ ├── page.tsx # Redirect → Anfragen
│ │ ├── login/page.tsx
│ │ ├── anfragen/page.tsx # Anfragen-Liste
│ │ ├── kalender/page.tsx # Blocks sperren/freigeben
│ │ └── wohnungen/page.tsx # Wohnungen pflegen
│ └── api/
│ ├── inquiries/route.ts # POST (öffentlich)
│ ├── availability/[slug]/route.ts # GET (öffentlich)
│ └── admin/
│ ├── login/route.ts
│ ├── logout/route.ts
│ ├── inquiries/[id]/route.ts # PATCH, DELETE
│ ├── blocks/route.ts # POST
│ ├── blocks/[id]/route.ts # DELETE
│ └── apartments/[id]/route.ts # PATCH
├── components/
│ ├── layout/ (Header, Footer)
│ ├── home/ (Hero, About, ApartmentPreview, Location)
│ ├── apartment/ (Gallery, Features, AvailabilityCalendar)
│ ├── inquiry/ (InquiryForm)
│ ├── admin/ (AdminNav, InquiryRow, CalendarManager, ApartmentEditor)
│ └── ui/ (Button, Input, Label, Textarea, FieldError)
├── lib/
│ ├── db.ts # Prisma-Singleton
│ ├── auth.ts # JWT-Session via jose
│ ├── email.ts # Nodemailer + Mail-Templates
│ ├── validations.ts # Zod-Schemas
│ └── utils.ts # Preis-/Datumsformatierung
├── prisma/
│ ├── schema.prisma # Apartment, Inquiry, Block, Admin
│ └── seed.ts # 2 Wohnungen + Admin + 1 Beispiel-Block
├── types/
├── middleware.ts # schützt /admin und /api/admin
├── tailwind.config.ts
├── next.config.js
├── tsconfig.json
├── .env.example
└── package.json
Setup
1. Abhängigkeiten installieren
npm install
2. Environment vorbereiten
cp .env.example .env
Dann .env öffnen und mindestens setzen:
AUTH_SECRET— zufällige, mind. 32 Zeichen lange Zeichenkette
(z. B.openssl rand -base64 48)ADMIN_EMAILundADMIN_PASSWORD— initialer Admin-AccountOWNER_EMAIL— wohin sollen neue Anfragen gehen?
Die SMTP-Variablen sind optional. Ohne sie werden Mails ins Terminal geloggt (praktisch fürs lokale Entwickeln).
3. Datenbank + Seed
npm run setup
Das führt die Prisma-Migration aus und legt die zwei Beispiel-Wohnungen samt Admin-Account an.
4. Dev-Server
npm run dev
→ http://localhost:3000
→ Admin: http://localhost:3000/admin/login
Nützliche Scripts
| Script | Zweck |
|---|---|
npm run dev |
Dev-Server mit Hot-Reload |
npm run build |
Production-Build (inkl. prisma generate + migrate) |
npm run start |
Production-Server |
npm run db:push |
Schema ohne Migration in DB pushen (prototyping) |
npm run db:migrate |
Neue Migration erzeugen |
npm run db:seed |
Seed erneut laufen lassen |
npm run db:studio |
Prisma Studio öffnen (DB-Browser) |
Design-System
- Farben:
parchment(Hintergrund),ink(Text),moss&sand(Akzente) - Typografie: Fraunces (Display) + Figtree (Body), geladen via
next/font - Layout: Container mit großzügigem Whitespace, asymmetrische Grids
- Bildsprache: Große Bilder, ruhige Kompositionen, dezente Hover-Zooms
Alle Tokens in tailwind.config.ts.
Wichtige Designentscheidungen
Warum SQLite?
Ein Reiseobjekt mit zwei Wohnungen erzeugt wenig Schreiblast. SQLite ist schnell, wartungsarm und das Schema ist 1:1 auf Postgres/MySQL portierbar, sobald gebraucht.
Warum JWT-Cookie statt NextAuth?
Für einen Admin-User + zwei Gastgeber ist NextAuth Overkill. jose liefert
signierte Cookies in ~80 Zeilen Code, die Middleware-kompatibel (Edge) funktionieren.
Warum Block.source trotz MVP?
Damit der spätere iCal-Importer einfach zusätzliche Einträge mit
source: "airbnb" / source: "booking" schreiben kann — ohne Schema-Änderung.
Honeypot statt Captcha
Das Anfrageformular hat ein verstecktes website-Feld als Bot-Falle.
Für den MVP ausreichend; falls Spam zunimmt, lässt sich hCaptcha o. ä. ergänzen.
Erweiterungspfad
Der Code ist bewusst so strukturiert, dass die folgenden Features additiv eingebaut werden können — ohne Refactoring:
iCal-Sync (Airbnb / Booking.com)
- Cron-Job (z. B. via Vercel Cron oder externer Scheduler), der pro Wohnung
eine
ics_import_urllädt, parst und Blocks mitsource: "airbnb"/source: "booking"anlegt. Schema muss ggf. umApartment.icalUrlserweitert werden. - Umgekehrt: GET-Endpoint
/api/ical/[slug].ics, der alle Blocks als ICS ausliefert, damit Airbnb/Booking wiederum eure Belegung sehen.
Direktbuchung
- Stripe/Mollie-Integration im
/api/bookings/route.ts. Booking-Tabelle einführen; erfolgreiche Zahlungen legen automatisch einenBlockmitsource: "direct"an.
Mehr Inhalte
- Tagespreise / Saisonpreise (neues
PriceRule-Modell). - Blog/Storys (neues
Post-Modell +/journal-Route).
Bildupload
- Aktuell pflegt der Editor Bilder als URLs. Für echtes Hochladen:
S3/R2/Cloudinary-Adapter in
lib/storage.tsund Drag-and-Drop-Komponente imApartmentEditor.
DSGVO-Notizen
- Keine Marketing-Cookies, kein Tracking.
- Nur technisch notwendige Session-Cookies (httpOnly, SameSite=Lax, Secure in Prod).
- Anfragen werden mit ausdrücklicher Einwilligung verarbeitet (Checkbox im Formular).
- Datenschutz- und Impressums-Seite als Platzhalter angelegt — vor Go-Live rechtlich prüfen lassen.
Lizenz
Privat / intern. Anpassen wie benötigt.