Blog • 21. September 2025 • von Unknown Author
Die Architektur im Detail
Blog • 21. September 2025 • von Unknown Author
Die Architektur im Detail

Next.js, Payload CMS und die Macht der Composability
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.
// 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 (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.
Die Lösung mit Server Components
// 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 ist nicht "noch ein Headless CMS". Es ist von Grund auf für moderne Architekturen konzipiert, mit TypeScript als First-Class-Citizen.
// Automatisch generierte TypeScript-Typesexport 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:
// 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:
// /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:
-- 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:
// 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:
// 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
// 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
// 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:
// 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:
pnpm generate:types # Generiert Types aus Payload-Schema
pnpm dev # Next.js Fast Refresh + Payload Admin
pnpm lint # ESLint + Prettier
pnpm migrate:create # Erstellt neue Migration
pnpm migrate:run # Führt Migrations aus
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
// 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
// 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 vorgestellte Architektur ist kein zufälliges Zusammenspiel von Technologien. Jede Komponente wurde bewusst gewählt und optimiert, um zusammen ein System zu schaffen, das:
Diese Architektur ist nicht theoretisch – sie läuft produktiv, sie skaliert, sie liefert messbare Geschäftsergebnisse.
✅ React Server Components reduzieren Bundle-Größen um 70%
✅ Payload CMS bietet Enterprise-Features out-of-the-box
✅ Service Layer Pattern sorgt für Clean Architecture
✅ PostgreSQL garantiert Datenintegrität
✅ Component-Driven Development ermöglicht Wiederverwendung
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 →
Dies ist Teil 2 einer 5-teiligen Serie über Enterprise-Ready Multi-Tenant-Architekturen.
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.
Artikel Details
- Autor
- Unknown Author
- Veröffentlicht
- 21. September 2025
- Lesezeit
- ca. 5 Min.