Blog • 21. September 2025

Die Architektur im Detail

Hero image

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.

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:

  1. Zentrale Business Logic: Alle Geschäftsregeln an einem Ort
  2. Type Safety: Durchgängige TypeScript-Unterstützung
  3. Caching: Automatisches Request-Level-Caching
  4. 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-Niederlassung
  • shop.ihrefirma.de → Online-Shop
  • partner.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
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

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.

Artikel Details

Veröffentlicht
21. September 2025
Lesezeit
ca. 5 Min.

Teilen

Weitere Artikel

KI-Realitätscheck 1. bis 14. Juni 2026: Was sich wirklich bewegt hat

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".

  • 17. Juni 2026
  • von Unknown Author
Weiterlesen
KI-Realitätscheck 2026: Was zwischen April und Juni wirklich passiert ist (und was davon nur Lärm war)

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.

  • 03. Juni 2026
  • von Unknown Author
Weiterlesen
Digitale Renaissance im Fokus: Von der Vision zur Bewegung (Mission) - ein Aufruf zum gemeinsamen Handeln!

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.

  • 31. März 2026
  • von Unknown Author
Weiterlesen