
Die Architektur im Detail
Next.js, Payload CMS und die Macht der Composability
Die Architektur im Detail
Next.js, Payload CMS und die Macht der Composability
Teil 2 der Serie "Enterprise-Ready Multi-Tenant-Architekturen mit Next.js und Payload CMS"
Nachdem wir im ersten Artikel die geschäftlichen Vorteile moderner Architekturen beleuchtet haben, öffnen wir heute die Motorhaube. Wie erreicht man PageSpeed-Scores von 95+? Wie verwaltet man dutzende Mandanten mit einer Codebasis? Die Antwort liegt in der durchdachten Kombination modernster Technologien und bewährter Patterns.
Der Technology Stack: Best-of-Breed statt Kompromisse
Moderne Architekturen folgen dem Prinzip der Composability: Statt auf monolithische All-in-One-Lösungen zu setzen, komponieren wir die besten Tools für jeden Zweck zu einem harmonischen Ganzen.
```typescript
// Unser Enterprise Stack im Überblick
const enterpriseStack = {
frontend: {
framework: "Next.js 15.1.6",
ui: "React 19 mit Server Components",
styling: "Tailwind CSS + CSS Modules",
types: "TypeScript durchgängig"
},
backend: {
cms: "Payload CMS 3.48",
database: "PostgreSQL 16",
storage: "Vercel Blob Storage",
auth: "NextAuth + Payload Users"
},
infrastructure: {
hosting: "Vercel Edge Network",
cdn: "Global Edge Caching",
monitoring: "Vercel Analytics",
ci: "GitHub Actions"
}
}
```
Jede Komponente wurde bewusst gewählt und spielt eine spezifische Rolle im Gesamtsystem.
React Server Components: Die Revolution der Webentwicklung
React Server Components (RSC) sind der Game-Changer für Performance. Statt den gesamten React-Code an den Browser zu senden, wird er auf dem Server ausgeführt. Das Ergebnis: 70% kleinere JavaScript-Bundles.
Das Problem mit traditionellem React
```jsx
// Traditionell: Alles wird zum Client geschickt
'use client'
export function ProductList() {
const [products, setProducts] = useState([])
useEffect(() => {
// Daten werden client-seitig geladen
fetch('/api/products')
.then(res => res.json())
.then(setProducts)
}, [])
// Loading State, Error Handling, Re-rendering...
return <div>{/* Komplexe Client-Logik */}</div>
}
// Bundle-Größe: ~150KB nur für diese Komponente
```
Die Lösung mit Server Components
```tsx
// Modern: Ausführung auf dem Server
export async function ProductList() {
// Direkte Datenbankabfrage, kein API-Call nötig
const products = await getProductsFromDB()
// HTML wird fertig gerendert zum Client geschickt
return (
<div className="grid grid-cols-3 gap-4">
{products.map(product => (
<ProductCard key={product.id} {...product} />
))}
</div>
)
}
// Bundle-Größe: 0KB - nur HTML wird übertragen
```
Der Unterschied ist dramatisch: Keine Loading States, kein Layout Shift, sofortiger Content.
Payload CMS: Enterprise-CMS neu gedacht
Payload CMS ist nicht "noch ein Headless CMS". Es ist von Grund auf für moderne Architekturen konzipiert, mit TypeScript als First-Class-Citizen.
TypeScript durchgängig
```typescript
// Payload Collection Definition
export const Products: CollectionConfig = {
slug: 'products',
access: {
read: publicTenantReadAccess,
create: superAdminOrTenantAdmin,
},
fields: [
{
name: 'title',
type: 'text',
required: true,
localized: true,
},
{
name: 'price',
type: 'number',
min: 0,
required: true,
},
{
name: 'tenant',
type: 'relationship',
relationTo: 'tenants',
required: true,
hasMany: false,
}
],
hooks: {
beforeChange: [ensureTenantIsolation],
afterRead: [enrichWithComputedFields],
}
}
// Automatisch generierte TypeScript-Types
export interface Product {
id: string
title: string
price: number
tenant: string | Tenant
createdAt: string
updatedAt: string
}
```
Diese Types fließen durch die gesamte Anwendung – von der Datenbank bis zum Frontend. Keine Runtime-Fehler mehr durch Tippfehler oder falsche Annahmen.
Das Multi-Tenant Plugin
Das Herzstück unserer Architektur ist das offizielle Multi-Tenant-Plugin von Payload:
```typescript
// Multi-Tenant Konfiguration
import { multiTenantPlugin } from '@payloadcms/plugin-multi-tenant'
export default buildConfig({
plugins: [
multiTenantPlugin({
tenantCollection: 'tenants',
tenantField: 'tenant',
isolationStrategy: 'filter', // Automatische Filterung
sharedCollections: ['media'], // Geteilte Ressourcen
})
],
// Rest der Konfiguration
})
```
Mit dieser Konfiguration wird jede Abfrage automatisch nach Mandant gefiltert. Keine Chance für Datenlecks zwischen Mandanten.
Die Service Layer: Clean Architecture in der Praxis
Zwischen Frontend und CMS liegt unsere Service Layer – das Gehirn der Anwendung:
```typescript
// /lib/payload-services.ts
import { cache } from 'react'
import { getPayload } from 'payload'
// Request-Level Caching mit React.cache
export const getTenantBySlug = cache(async (slug: string) => {
const payload = await getPayload()
const result = await payload.find({
collection: 'tenants',
where: {
slug: { equals: slug }
},
depth: 2 // Lade Beziehungen mit
})
return result.docs[0] || null
})
// Tenant-aware Datenabfrage
export const getProductsByTenant = cache(async (
tenantId: string,
options?: {
limit?: number
category?: string
featured?: boolean
}
) => {
const payload = await getPayload()
const where: Where = {
tenant: { equals: tenantId },
status: { equals: 'published' }
}
if (options?.category) {
where.category = { equals: options.category }
}
if (options?.featured) {
where.featured = { equals: true }
}
const result = await payload.find({
collection: 'products',
where,
limit: options?.limit || 10,
sort: '-createdAt'
})
return result.docs
})
```
Diese Service Layer bietet mehrere Vorteile:
- Zentrale Business Logic: Alle Geschäftsregeln an einem Ort
- Type Safety: Durchgängige TypeScript-Unterstützung
- Caching: Automatisches Request-Level-Caching
- Testbarkeit: Services können isoliert getestet werden
PostgreSQL: Die Basis für Datenintegrität
Während NoSQL-Datenbanken ihren Platz haben, bietet PostgreSQL für Multi-Tenant-Systeme unschlagbare Vorteile:
```sql
-- Automatische Tenant-Isolation auf Datenbankebene
CREATE POLICY tenant_isolation ON products
FOR ALL
USING (tenant_id = current_setting('app.current_tenant')::uuid);
-- Referentielle Integrität
ALTER TABLE products
ADD CONSTRAINT fk_tenant
FOREIGN KEY (tenant_id)
REFERENCES tenants(id)
ON DELETE CASCADE;
-- Performante Indizes für Multi-Tenant-Abfragen
CREATE INDEX idx_products_tenant_status
ON products(tenant_id, status)
WHERE deleted_at IS NULL;
```
PostgreSQL garantiert ACID-Compliance, unterstützt komplexe Abfragen und skaliert vertikal bis zu enormen Datenmengen.
Das Routing-System: Subdomain-Magic
Wie erkennt das System automatisch den richtigen Mandanten? Durch intelligentes Routing:
```typescript
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export async function middleware(request: NextRequest) {
const hostname = request.headers.get('host') || ''
// Extrahiere Subdomain
const subdomain = hostname.split('.')[0]
// Entwicklung: subdomain.localhost:3000
// Produktion: subdomain.ihredomain.de
if (subdomain && subdomain !== 'www' && subdomain !== 'admin') {
// Rewrite zu tenant-spezifischer Route
return NextResponse.rewrite(
new URL(/${subdomain}${request.nextUrl.pathname}, request.url)
)
}
return NextResponse.next()
}
export const config = {
matcher: ['/((?!api|_next/static|favicon.ico).*)']
}
```
Dieses Pattern ermöglicht URLs wie:
berlin.ihrefirma.de→ Berlin-Niederlassungshop.ihrefirma.de→ Online-Shoppartner.ihrefirma.de→ Partner-Portal
Alles mit derselben Codebasis.
Component-Driven Development: Der Baukasten-Ansatz
Moderne Architekturen sind modular. Jede Komponente ist ein eigenständiger, wiederverwendbarer Baustein:
```tsx
// Basis-Komponente mit Tenant-Theming
export const Button: React.FC<ButtonProps> = ({
children,
variant = 'primary',
tenantTheme
}) => {
const styles = {
primary: {
backgroundColor: tenantTheme?.primaryColor || '#0070f3',
color: tenantTheme?.primaryTextColor || '#ffffff'
},
secondary: {
borderColor: tenantTheme?.primaryColor || '#0070f3',
color: tenantTheme?.primaryColor || '#0070f3'
}
}
return (
<button
className="px-6 py-3 rounded-lg transition-all"
style={styles[variant]}
>
{children}
</button>
)
}
// Verwendung in verschiedenen Kontexten
<Button tenantTheme={berlinTheme}>Jetzt kaufen</Button>
<Button tenantTheme={münchenTheme} variant="secondary">Mehr erfahren</Button>
```
Einmal entwickelt, überall einsetzbar, individuell anpassbar.
Performance-Optimierung: Built-in, nicht Bolt-on
Performance ist kein nachträgliches Feature, sondern integraler Bestandteil der Architektur:
Parallele Datenabfragen
```typescript
// Schlecht: Sequentielle Abfragen
const tenant = await getTenant(slug)
const pages = await getPages(tenant.id) // Wartet auf tenant
const posts = await getPosts(tenant.id) // Wartet auf pages
// Gesamtzeit: 300ms + 200ms + 250ms = 750ms
// Gut: Parallele Abfragen
const [tenant, settings] = await Promise.all([
getTenant(slug),
getGlobalSettings()
])
const [pages, posts, products] = await Promise.all([
getPages(tenant.id),
getPosts(tenant.id),
getProducts(tenant.id)
])
// Gesamtzeit: max(300ms, 250ms) = 300ms
```
Edge Caching
```typescript
// Statische Seiten mit ISR (Incremental Static Regeneration)
export const revalidate = 3600 // Cache für 1 Stunde
export async function generateStaticParams() {
const tenants = await getAllTenants()
const pages = await getAllPages()
return tenants.flatMap(tenant =>
pages.map(page => ({
tenantSlug: tenant.slug,
pageSlug: page.slug
}))
)
}
// Seiten werden beim ersten Besuch generiert und gecacht
```
Access Control: Sicherheit by Design
Multi-Tenant-Systeme müssen Daten strikt isolieren. Unser Access Control System garantiert dies:
```typescript
// Standardisierte Access Control Patterns
export const publicTenantReadAccess: Access = ({ req }) => {
// Öffentliche Leser sehen nur ihren Mandanten
if (!req.user) {
return {
tenant: {
equals: req.context?.tenant?.id
}
}
}
return true
}
export const tenantAdminAccess: Access = ({ req }) => {
const user = req.user
// Super-Admins haben vollen Zugriff
if (user?.role === 'super-admin') return true
// Tenant-Admins nur auf ihren Bereich
if (user?.role === 'tenant-admin') {
return {
tenant: {
equals: user.tenant?.id
}
}
}
return false
}
```
Diese Patterns werden konsequent auf alle Collections angewendet. Kein Datenleck möglich.
Die Entwickler-Experience: Produktivität durch Tooling
Eine gute Architektur macht Entwickler produktiv und glücklich:
```bash
Type-Safe von Anfang an
pnpm generate:types # Generiert Types aus Payload-Schema
Hot Reload in < 1 Sekunde
pnpm dev # Next.js Fast Refresh + Payload Admin
Automatische Formatierung
pnpm lint # ESLint + Prettier
Database Migrations
pnpm migrate:create # Erstellt neue Migration
pnpm migrate:run # Führt Migrations aus
Testing
pnpm test # Jest + React Testing Library
pnpm test:e2e # Playwright E2E Tests
```
Die Werkzeuge arbeiten nahtlos zusammen. Keine Konfigurationshölle, keine Inkompatibilitäten.
Real-World Patterns: Aus der Praxis für die Praxis
Pattern 1: Lazy Loading für Heavy Components
```tsx
// Dynamischer Import für große Komponenten
import dynamic from 'next/dynamic'
const InteractiveMap = dynamic(
() => import('@/components/InteractiveMap'),
{
loading: () => <MapSkeleton />,
ssr: false // Nur client-seitig laden
}
)
// Komponente wird erst geladen, wenn benötigt
export function ContactPage() {
return (
<div>
<h1>Kontakt</h1>
<Suspense fallback={<MapSkeleton />}>
<InteractiveMap />
</Suspense>
</div>
)
}
```
Pattern 2: Optimistic Updates
```tsx
// Sofortiges UI-Feedback, während Server arbeitet
export function LikeButton({ postId, initialLikes }) {
const [likes, setLikes] = useState(initialLikes)
const [isLiking, setIsLiking] = useState(false)
async function handleLike() {
// Optimistic Update
setLikes(prev => prev + 1)
setIsLiking(true)
try {
await likePost(postId)
} catch (error) {
// Rollback bei Fehler
setLikes(prev => prev - 1)
toast.error('Fehler beim Liken')
} finally {
setIsLiking(false)
}
}
return (
<button onClick={handleLike} disabled={isLiking}>
❤️ {likes}
</button>
)
}
```
Die Architektur-Entscheidungen: Das "Warum" hinter dem "Was"
Warum Next.js statt Gatsby/Remix/Astro?
- App Router: Modernste React-Features out-of-the-box
- Vercel-Integration: Optimales Deployment und Edge Functions
- Hybrid Rendering: SSG, SSR und ISR in einer App
- Community: Größtes Ökosystem, beste Unterstützung
Warum Payload CMS statt Strapi/Directus/Sanity?
- TypeScript-First: Durchgängige Type-Safety
- Code-basierte Konfiguration: Versionierbar, reviewbar
- Multi-Tenant-Plugin: Enterprise-Feature out-of-the-box
- Local API: Keine HTTP-Overhead für Server-Abfragen
Warum PostgreSQL statt MongoDB/MySQL?
- ACID-Compliance: Transaktionale Integrität
- JSON-Support: Flexibilität wo nötig
- Row-Level Security: Native Multi-Tenant-Unterstützung
- Bewährt: 30+ Jahre Entwicklung und Optimierung
Fazit: Die Summe ist größer als ihre Teile
Die vorgestellte Architektur ist kein zufälliges Zusammenspiel von Technologien. Jede Komponente wurde bewusst gewählt und optimiert, um zusammen ein System zu schaffen, das:
- Performant ist (95+ PageSpeed)
- Skalierbar wächst (unbegrenzte Mandanten)
- Wartbar bleibt (klare Patterns)
- Sicher operiert (strikte Isolation)
- Entwicklerfreundlich funktioniert (moderne Tools)
Diese Architektur ist nicht theoretisch – sie läuft produktiv, sie skaliert, sie liefert messbare Geschäftsergebnisse.
Key Technical Takeaways
✅ React Server Components reduzieren Bundle-Größen um 70%
- Weniger JavaScript = schnellere Ladezeiten
✅ Payload CMS bietet Enterprise-Features out-of-the-box
- Multi-Tenant, Versionierung, Lokalisierung
✅ Service Layer Pattern sorgt für Clean Architecture
- Testbar, wartbar, erweiterbar
✅ PostgreSQL garantiert Datenintegrität
- ACID, Constraints, Row-Level Security
✅ Component-Driven Development ermöglicht Wiederverwendung
- Einmal bauen, überall nutzen
Im nächsten Artikel: "Performance als Wettbewerbsvorteil" – Wir zeigen konkret, wie wir 95+ PageSpeed Scores erreichen und was das für Ihr Business bedeutet.
Bleiben Sie dran für Teil 3 der Serie →
Über diese Serie
Dies ist Teil 2 einer 5-teiligen Serie über Enterprise-Ready Multi-Tenant-Architekturen.
KI-Realitätscheck 1. bis 14. Juni 2026: Was sich wirklich bewegt hat
Die Halbwertszeit einer KI-Schlagzeile liegt weiter bei etwa 48 Stunden. In den ersten zwei Juni-Wochen wurde aus "der Bundestag verabschiedet ein Durchführungsgesetz" ein "Deutschland reguliert KI scharf", aus einem freiwilligen Verhaltenskodex eine "Pflicht zum Wasserzeichen", und aus einer Exportkontroll-Anordnung ein "Anthropic schaltet Claude ab".
KI-Realitätscheck 2026: Was zwischen April und Juni wirklich passiert ist (und was davon nur Lärm war)
Die Halbwertszeit einer KI-Schlagzeile liegt bei ungefähr 48 Stunden. In dieser Zeitspanne wird aus "Microsoft schränkt intern ein Tool ein" ein "Microsoft wirft Claude raus", aus "ein Automatisierungstool verdoppelt seinen Wert" ein "n8n ist tot", und aus einer verschobenen Frist ein "die EU kippt das KI-Gesetz". Wer beruflich mit künstlicher Intelligenz arbeitet, steht damit vor einem doppelten Problem: Man muss nicht nur die Entwicklungen kennen, sondern auch laufend zwischen Signal und Lärm unterscheiden.
Digitale Renaissance im Fokus: Von der Vision zur Bewegung (Mission) - ein Aufruf zum gemeinsamen Handeln!
Die Digitale Renaissance ist eine solche Idee – geboren aus der Erkenntnis, dass wir an einem historischen Wendepunkt stehen. Der Wendepunkt kann auch ein Scheidepunkt zwischen positiven und weniger positiven Zukunftsszenarien sein.