Het beheren van één hotelwebsite is eenvoudig. Dertig? Daar begint de meeste teams beslissingen te nemen waar ze jaren spijt van zullen hebben. Ik heb hotelgroepen zien improviseren met aparte WordPress-installaties per eigenschap, page builders op monolithische CMS-platforms plakken, en zes cijfers budgetten verbranden aan enterprise-oplossingen die nog steeds niet een nieuwe eigenschap in minder dan drie maanden kunnen lanceren.

Er is een betere manier. Een goed gearchitecteerde Next.js-applicatie kan elke eigenschap in een hotelgroep bedienen vanuit één codebase, één deployment pipeline en één contentbeheerslaag. Elke eigenschap krijgt zijn eigen branding, eigen content, eigen domein. Het engineeringteam krijgt zijn gezond verstand terug.

Dit artikel beschrijft exact hoe je dat systeem bouwt. Niet theorie — echte architectuurpatronen die we op echte hotelgroepprojecten hebben gebruikt.

Inhoudsopgave

Hotel Group Websites: Multi-Property Architecture with Next.js

Waarom hotelgroepen een geïntegreerd platform nodig hebben

De typische hotelgroepwebsitesituatie ziet er zo uit: Eigenschap A werkt met WordPress met een thema uit 2019. Eigenschap B staat op Squarespace omdat de GM's neef het opzette. Eigenschap C heeft een aangepaste PHP-site waar niemand aan wil zitten. De bedrijfssite staat op een geheel ander platform.

Elke update van een eigenschap vereist een ander workflow. Merkconsistentie is een droom. SEO-strategie is verspreid over tientallen domeinen zonder gedeelde autoriteit. Wanneer de afdeling corporate besluit een nieuw ameniteitsbadge toe te voegen of de boeking widget bij te werken, moet iemand die wijziging op 15 verschillende plaatsen aanbrengen.

De kosten stapelen zich op:

  • Onderhoudsoverhead: Elk platform heeft zijn eigen hosting, beveiligingspatches, pluginupdates nodig
  • Merkdrift: Eigenschappen wijken langzaam af van merkrichtlijnen
  • Contextomschakeling voor ontwikkelaars: Je team (of bureau) heeft expertise nodig over meerdere platforms
  • Traag lanceren van eigenschappen: Nieuwe verwervingen duren maanden om online te gaan
  • Gefragmenteerde analytics: Geen geünificeerde weergave van prestaties over de portefeuille

Een gecentraliseerd multi-property platform lost dit allemaal op. Eén codebase. Één deployment. Eén CMS. Content en branding per eigenschap geleverd via configuratie, niet aparte codebases.

Architectuuroverzicht: één codebase, veel eigenschappen

Hier is de architectuur op hoog niveau die werkt:

┌─────────────────────────────────────────────┐
│              CDN / Edge Network               │
│         (Vercel, Cloudflare, Fastly)          │
├─────────────────────────────────────────────┤
│           Next.js Application                 │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐     │
│  │ Property  │ │ Property  │ │ Property  │     │
│  │ Resolver  │ │ Theming   │ │ Content   │     │
│  │ Middleware│ │ Engine    │ │ Fetcher   │     │
│  └──────────┘ └──────────┘ └──────────┘     │
├─────────────────────────────────────────────┤
│              API Layer                        │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐     │
│  │ Headless  │ │ Booking   │ │ Media     │     │
│  │ CMS       │ │ Engine    │ │ CDN       │     │
│  └──────────┘ └──────────┘ └──────────┘     │
└─────────────────────────────────────────────┘

De Next.js-app fungeert als de renderinglaag. Middleware bepaalt welke eigenschap wordt aangevraagd (via domein, subdomein of pad). De themingengine past eigenschapsspecifieke stijlen toe. De content fetcher haalt eigenschapsbereiktecontent uit de headless CMS.

Alles stroomafwaarts — de CMS, boeking engine, mediaopslag — wordt bevraagd met een eigenschapsaanduiding. Die aanduiding is de draad die het hele systeem bijeenbindt.

Multi-tenancy-patronen in Next.js

Er zijn drie primaire benaderingen voor multi-tenancy in Next.js. Elk heeft voor- en nadelen.

Patroon 1: Subdomeingebaseerde routering

Elke eigenschap krijgt een subdomein: grandplaza.hotelgroup.com, seasideresort.hotelgroup.com.

Next.js middleware onderschept het verzoek, extraheert het subdomein en lost de eigenschapsconfiguratie op:

// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
import { getPropertyByDomain } from '@/lib/properties';

export function middleware(request: NextRequest) {
  const hostname = request.headers.get('host') || '';
  const subdomain = hostname.split('.')[0];
  
  const property = getPropertyByDomain(subdomain);
  
  if (!property) {
    return NextResponse.redirect(new URL('/not-found', request.url));
  }
  
  // Inject property context into headers for downstream use
  const response = NextResponse.next();
  response.headers.set('x-property-id', property.id);
  response.headers.set('x-property-slug', property.slug);
  
  return response;
}

Voordelen: Schone URL's, gemakkelijke eigenschapsisolatie, goed voor SEO als eigenschappen geen aparte TLD's nodig hebben.
Nadelen: SSL-certificaatbeheer voor wildcards, minder merkonafhankelijkheid per eigenschap.

Patroon 2: Aangepaste domeinutwisseling

Elke eigenschap heeft zijn eigen domein: grandplazahotel.com, seasideresort.com.

Dit is wat de meeste hotelgroepen werkelijk willen. De middleware-logica is vergelijkbaar, maar je vergelijkt met een domeinopzoektabel:

const DOMAIN_MAP: Record<string, string> = {
  'grandplazahotel.com': 'grand-plaza',
  'www.grandplazahotel.com': 'grand-plaza',
  'seasideresort.com': 'seaside-resort',
  'www.seasideresort.com': 'seaside-resort',
};

Vercel ondersteunt aangepaste domeinen per project van nature, en je kunt tot 50 domeinen toewijzen op hun Pro-plan ($20/maand vanaf 2025). Voor grotere portefeuilles verwijdert hun Enterprise-plan die limiet.

Voordelen: Volledige merkonafhankelijkheid, bestaande domeinaanwezigheid behouden.
Nadelen: DNS-beheeroverhoofdse, complexere SSL-provisioning.

Patroon 3: Padgebaseerde routering

Alle eigenschappen onder één domein: hotelgroup.com/properties/grand-plaza, hotelgroup.com/properties/seaside-resort.

Voordelen: Eenvoudigste implementatie, geconsolideerde domeinautoriteit voor SEO.
Nadelen: Minder merkidentiteit per eigenschap, URL-structuur voelt bedrijfsmatig.

Patroon Merkonafhankelijkheid SEO-flexibiliteit Implementatiecomplexiteit Ideaal voor
Subdomein Gemiddeld Gemiddeld Laag Bespaaringsgezinde groepen
Aangepast domein Hoog Hoog Gemiddeld Gevestigde merken
Padgebaseerd Laag Hoog (geconsolideerd) Laagst Nieuwe portefeuille-websites

De meeste hotelgroepen waarmee we werken bij Social Animal kiezen uiteindelijk voor aangepaste domeinutwisseling. Eigenschappen hebben merkwaarde in hun domeinen, en marketingteams willen de onafhankelijkheid.

Hotel Group Websites: Multi-Property Architecture with Next.js - architecture

Headless CMS-strategie voor hotelgroepen

De CMS-keuze maakt of breekt deze architectuur. Je hebt een systeem nodig dat multi-tenancy op contentniveau ondersteunt — waarbij editors voor eigenschap A niet per ongeluk eigenschap B's content kunnen wijzigen, maar bedrijfsadmins alles kunnen beheren.

CMS-opties die goed werken

Sanity is mijn beste keuze voor hotelgroepen. De documentniveaupermissies, aangepaste studioconfiguratie en GROQ-querytaal maken eigenschapsbereiktecontent ophalen triviaal. Je kunt een enkele Sanity Studio met workspace-per-eigenschap weergaven bouwen. Prijzen beginnen bij $99/maand voor het Team-plan (2025-prijzen), en het schaalt goed naar grote contenthoeveelheden.

Contentful werkt als je al in hun ecosysteem zit. Hun ruimteniveauisolatie brengt goed in kaart met eigenschappen, hoewel het duur kan worden — elke ruimte op het Premium-plan voegt kosten toe, en je kijkt naar $2.500+/maand voor ondernemingsschaal hotelgroepbehoeften.

Strapi (zelf gehost) is de budgetoptie. Je zult de multi-tenancy-laag zelf moeten bouwen met aangepaste middleware en op rollen gebaseerde toegangscontrole, maar er zijn geen per-seat licentiekosten.

We behandelen het volledige CMS-selectieproces in onze headless CMS-onwikkelingsgids.

Contentmodellering voor hotels

Hier is een contentmodel dat over eigenschappen werkt:

// Sanity schema example
export const property = defineType({
  name: 'property',
  title: 'Property',
  type: 'document',
  fields: [
    defineField({ name: 'name', type: 'string' }),
    defineField({ name: 'slug', type: 'slug' }),
    defineField({ name: 'domain', type: 'string' }),
    defineField({ name: 'brand', type: 'reference', to: [{ type: 'brand' }] }),
    defineField({ name: 'location', type: 'geopoint' }),
    defineField({ name: 'theme', type: 'propertyTheme' }),
    defineField({ name: 'bookingEngineId', type: 'string' }),
  ],
});

export const room = defineType({
  name: 'room',
  title: 'Room Type',
  type: 'document',
  fields: [
    defineField({ name: 'property', type: 'reference', to: [{ type: 'property' }] }),
    defineField({ name: 'name', type: 'string' }),
    defineField({ name: 'description', type: 'blockContent' }),
    defineField({ name: 'maxOccupancy', type: 'number' }),
    defineField({ name: 'amenities', type: 'array', of: [{ type: 'reference', to: [{ type: 'amenity' }] }] }),
    defineField({ name: 'gallery', type: 'array', of: [{ type: 'image' }] }),
  ],
});

Het sleutelpatroon: elk contentdocument verwijst naar een property. Query's filteren altijd op eigenschap. Editors zien alleen de content van hun eigenschap. Bedrijfsadmins zien alles.

Gedeelde componenten versus aanpassingen op eigenschapsniveau

Dit is waar de architectuur interessant wordt. Je wilt 80% van de componenten delen over eigenschappen, met 20% die aanpassingen op eigenschapsniveau toestaat.

De theminglaag

Maak een themaconfiguratie per eigenschap aan die in je componentsysteem stroomt:

// types/theme.ts
export interface PropertyTheme {
  colors: {
    primary: string;
    secondary: string;
    accent: string;
    background: string;
    text: string;
  };
  typography: {
    headingFont: string;
    bodyFont: string;
  };
  logo: {
    light: string;
    dark: string;
  };
  borderRadius: 'none' | 'sm' | 'md' | 'lg';
  heroStyle: 'fullbleed' | 'contained' | 'split';
}

Tailwind CSS v4 (uitgebracht in 2025) maakt dit aanzienlijk gemakkelijker met zijn CSS-first-configuratie en native theming-functieondersteuning. Je kunt CSS-aangepaste eigenschappen op layoutniveau instellen en ze door elke component laten cascaderen:

// app/layout.tsx
export default async function PropertyLayout({ children }: { children: React.ReactNode }) {
  const property = await getCurrentProperty();
  const theme = property.theme;
  
  return (
    <html
      style={{
        '--color-primary': theme.colors.primary,
        '--color-secondary': theme.colors.secondary,
        '--font-heading': theme.typography.headingFont,
        '--font-body': theme.typography.bodyFont,
      } as React.CSSProperties}
    >
      <body className="font-body text-text bg-background">
        {children}
      </body>
    </html>
  );
}

Componentsamenstelling

Gedeelde componenten accepteren themasymbolen en worden per eigenschap anders weergegeven zonder branchinglogica:

// components/HeroSection.tsx
export function HeroSection({ property }: { property: Property }) {
  const heroConfig = property.theme.heroStyle;
  
  const variants = {
    fullbleed: 'h-screen w-full',
    contained: 'h-[70vh] max-w-7xl mx-auto rounded-2xl overflow-hidden',
    split: 'grid grid-cols-2 h-[80vh]',
  };
  
  return (
    <section className={variants[heroConfig]}>
      {/* Shared hero content structure */}
    </section>
  );
}

Integratie boeking engine

Hotelwebsites bestaan om één reden: bookings stimuleren. De integratie van de boeking engine moet waterdicht zijn.

De meeste hotelgroepen gebruiken een van deze boeking engines: SynXis (Sabre), Pegasus, Bookassist, SiteMinder, of een eigen centraal reserveringssysteem. Het integratiepatroon is bijna altijd hetzelfde: geef een eigenschapsaanduiding, datumbereik en aantal gasten door om beschikbaarheid te krijgen.

// lib/booking.ts
export async function checkAvailability({
  propertyCode,
  checkIn,
  checkOut,
  adults,
  children,
}: BookingQuery) {
  const response = await fetch(`${BOOKING_ENGINE_URL}/availability`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${BOOKING_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      hotel_code: propertyCode,
      arrival: checkIn,
      departure: checkOut,
      guests: { adults, children },
    }),
  });
  
  return response.json();
}

Voor de boeking widget zelf heb je twee opties:

  1. Ingesloten iframe: De boeking engine biedt een widget aan die je insluit. Minste werk, minste controle.
  2. API-aangestuurde aangepaste UI: Je bouwt de zoek- en resultatgebruikersinterface, roept de boeking API rechtstreeks aan, en geeft af aan de boeking engine alleen voor betaling. Meer werk, veel betere UX.

Optie 2 is waar een Next.js-architectuur echt glanst. Je kunt een prachtige, snelle, merkgebonden boekingservaring bouwen die native aanvoelt voor elke eigenschap. Server Components kunnen beschikbaarheidsgegevens vooraf ophalen. De boekingsstroom blijft op je domein, wat beter is voor conversietracking en SEO.

Domeinrouting en eigenschapsresolutie

De eigenschapsresolutie flow moet snel zijn. Echt snel. Het loopt op elk verzoek.

Dit is het patroon dat in productie werkt:

  1. Edge middleware lost domein → eigenschaftslug op (in-memory opzoeken, sub-milliseconde)
  2. Eigenschapsconfiguratie wordt in de cache opgeslagen aan de rand met Vercel Edge Config of Cloudflare KV
  3. Volledige eigenschapsgegevens (thema, navigatie, voeterinhoud) worden eenmaal per build opgehaald via ISR of op aanvraagmoment met caching
// lib/property-resolver.ts
import { get } from '@vercel/edge-config';

export async function resolveProperty(hostname: string): Promise<PropertyConfig | null> {
  // First: check edge config (sub-5ms)
  const domainMap = await get<Record<string, string>>('domain-map');
  const propertySlug = domainMap?.[hostname];
  
  if (!propertySlug) return null;
  
  // Second: get full property config (cached)
  const propertyConfig = await get<PropertyConfig>(`property:${propertySlug}`);
  return propertyConfig;
}

Vercel Edge Config is perfect voor dit — het is een wereldwijd verspreide key-value store met leeslatentie onder 1ms. Het kost $0 op Pro-plans voor tot 512KB gegevens, wat genoeg is voor een opzoeking van een eigenaarproperty.

Prestaties op schaal

Hotelwebsites hebben specifieke prestatiekenmerken die belangrijk zijn:

  • Afbeeldingsrijke pagina's: Kamergarages, eigendomsfoto's, bestemmingsafbeeldingen
  • Seizoensgebonden verkeerspieken: Vakantieperioden, conventieperiode, lokale evenementen
  • Wereldwijd publiek: Internationale reizigers die overal bladeren
  • Conversie-kritisch: Elke 100ms laadtijd kost bookings

Statische generatiestrategie

Gebruik Incremental Static Regeneration (ISR) voor eigendomspagina's. Hotelinhoud verandert niet elke minuut — een hervalidatieperiode van 60 seconden is meestal prima:

// app/[propertySlug]/page.tsx
export async function generateStaticParams() {
  const properties = await getAllProperties();
  return properties.map((p) => ({ propertySlug: p.slug }));
}

export const revalidate = 60;

Voor een groep van 30 eigenschappen met ~20 pagina's per eigenschap, genereer je vooraf ~600 pagina's. Next.js verwerkt dit zonder veel moeite. Buildtijden blijven onder de 5 minuten.

Afbeeldingsoptimalisatie

Next.js Image component met een externe loader verwerkt per-eigendom afbeeldingsoptimalisatie. Als je Sanity gebruikt, is hun afbeeldings-CDN met automatische formaatconversie en formaat wijzigen uitstekend. Cloudinary is een ander solide alternatief voor $89/maand voor het Plus-plan.

Een typische hoteleigendomspagina moet gericht zijn op:

  • LCP onder 2,5s op 4G-verbindingen
  • CLS van 0 (geen lay-out verschuiving van laden van afbeeldingen)
  • Totaal paginagewicht onder 1,5MB bij initieel laden

Centraal beheerdashboard

Naast de CMS hebben hotelgroepen operationele dashboards nodig. Hier bouw je aangepaste tools:

  • Eigendomsoverzicht: Status van elke eigendomssite (live, staging, onderhoud)
  • Contentversheid: Welke eigenschappen hebben hun seizoensinhoud niet bijgewerkt
  • Prestatiestoking: Core Web Vitals per eigenschap
  • Analytics rollup: Boeking trechtermaten over alle eigenschappen

We bouwen dit meestal als een aparte Next.js-app (vaak met de App Router's server-side mogelijkheden) die verbinding maakt met dezelfde gegevensbronnen. Het beheerdashboard is een intern gereedschap — het hoeft niet flashy te zijn, maar het moet functioneel zijn.

Deployment en DevOps

Eén codebase betekent één CI/CD-pipeline. Dit is de implementatiestroom:

  1. Codewijzigingen: PR → review → samenvoegen naar main
  2. Build: Next.js bouwt alle statische pagina's over alle eigenschappen
  3. Deploy: Vercel (of vergelijkbaar) implementeert naar edge network
  4. DNS: Elk eigendomsdomein wijst naar de implementatie

Inhoudswijzigingen vereisen geen implementatie. De headless CMS triggert ISR-hervalidering via webhook:

// app/api/revalidate/route.ts
export async function POST(request: Request) {
  const body = await request.json();
  const { propertySlug, contentType } = body;
  
  // Revalidate specific paths for the changed property
  revalidatePath(`/${propertySlug}`);
  
  if (contentType === 'room') {
    revalidatePath(`/${propertySlug}/rooms`);
  }
  
  return Response.json({ revalidated: true });
}

Kostenvergelijking uit de praktijk

Laten we de werkelijke kosten vergelijken voor een hotelgroep met 20 eigenschappen:

Kostencategorie Aparte sites (WordPress) Geïntegreerd Next.js platform
Hosting (maandelijks) $2.000-4.000 (20 × beheerde WP) $150-400 (Vercel Pro/Team)
CMS-licentieverlening $0-600 (plugins per site) $99-300 (Sanity/Contentful)
SSL-certificaten $0-400 (als niet Let's Encrypt gebruikt) $0 (auto-ingericht)
Onderhoud (jaarlijks) $40.000-80.000 (updates, beveiliging) $10.000-20.000
Lancering nieuwe eigenschap $5.000-15.000 per site $500-2.000 (inhoud + configuratie)
Jaarlijks totaal (gesch.) $75.000-150.000 $15.000-35.000

De cijfers zijn niet eens in de buurt. En dit houdt geen rekening met verbeteringen in de ontwikkelaarsservaring — het hebben van één codebase betekent dat je team het systeem werkelijk begrijpt. Geen meer "welke WordPress-versie werkt die eigenschap?"

Voor hotelgroepen die deze benadering overwegen, hebben we onze Next.js-ontwikkelingscapaciteiten uiteengezet en je kunt onze prijsstructuur zien voor een gedetailleerder schatting.

Veelgestelde vragen

Hoe lang duurt het om een hotelgroep naar een geïntegreerd Next.js-platform te migreren? Voor een groep van 10-20 eigenschappen, verwacht 3-5 maanden van kickoff tot volledige lancering. De eerste eigenschap duurt het langst (8-10 weken) omdat je het platform bouwt. Elke volgende eigenschap is vooral contentmigratie en themaconfiguratie, wat 1-2 weken duurt. We lanceren meestal in golven — 3-4 eigenschappen tegelijk.

Kunnen individuele eigenschappen nog steeds unieke pagina's hebben die andere eigenschappen niet hebben? Absoluut. Het contentmodel ondersteunt eigenschapsspecifieke paginatypen. Als je badresort een sectie "Trouwlocaties" nodig heeft maar je zakenhotel niet, dat is een beslissing op contentniveau. Het CMS-schema ondersteunt optionele paginatypen, en de Next.js dynamische routering verwerkt het weergeven van elke pagina die in de CMS voor een bepaalde eigenschap aanwezig is.

Wat gebeurt er wanneer je een nieuw hotel verwerft en het aan het platform moet toevoegen? Dit is een van de grootste winsten. Het toevoegen van een nieuwe eigenschap betekent: een eigenschapsitem in de CMS maken, het thema configureren (kleuren, lettertypen, logo), de domeinutwisseling toevoegen en content invullen. Een competent content team kan een nieuwe eigenschap in 1-2 weken live hebben. Vergelijk dat met 2-3 maanden om een zelfstandige website te bouwen.

Hoe ga je met meertalige ondersteuning over eigenschappen in verschillende landen om? Next.js heeft ingebouwde ondersteuning voor i18n-routering. In combinatie met een headless CMS die gelokaliseerde content ondersteunt (Sanity en Contentful doen dit beiden goed), kun je elke eigenschap in zijn relevante talen bedienen. Een eigenschap in Barcelona heeft mogelijk Spaans, Catalaans, Engels en Frans nodig. Een eigenschap in Miami heeft mogelijk alleen Engels en Spaans nodig. Elke taalconfiguratie van de eigenschap is onafhankelijk.

Werkt deze architectuur ook met Astro in plaats van Next.js? Ja, en voor sommige hotelgroepen is het eigenlijk de betere keuze. Als je eigenschappen vooral contentgestuurd zijn met minimale interactiviteit (geen complex boekingsproces, bijvoorbeeld), Astro's multi-page-architectuur kan nog betere prestaties leveren met minder JavaScript. De multi-tenancy-patronen zijn vergelijkbaar — middleware-gebaseerde eigenschapsresolutie, headless CMS met eigenschapsbereik, themasymbolen per eigenschap.

Hoe ga je met SEO om wanneer eigenschappen op aparte domeinen staan maar worden bediend vanuit één applicatie? Elk eigendomsdomein krijgt zijn eigen sitemap, zijn eigen robots.txt, zijn eigen gestructureerde gegevens (Hotel-schemamarkering) en zijn eigen metatags. Vanuit Google's perspectief zijn dit volledig aparte websites. De canonieke URL's wijzen naar elk eigendomsdomein zelf. Je krijgt ook het voordeel van gecentraliseerde schemaopmaakgeneratie — elke eigenschap krijgt automatisch juiste JSON-LD voor hotels, kamers, reviews en lokale bedrijfsinformatie.

Wat te doen met eigenschapsspecifieke integraties zoals lokale activiteitenboekingen of spa-reserveringssystemen? De componentarchitectuur ondersteunt configuratie van eigenschapsintegratie. Elke eigenschapsconfiguratie in de CMS kan specificeren welke third-party integraties het gebruikt. De renderinglaag sluit die integratiecomponenten voorwaardelijk in. Een spaeigenschap krijgt de spa-boeking widget. Een zakelijk hotel in het centrum krijgt het configurator voor vergaderzalen. Deze worden geladen als dynamische imports, dus ze beïnvloeden niet de bundle-grootte voor eigenschappen die ze niet gebruiken.

Is er een risico dat verkeerspiek van één eigenschap andere eigenschappen beïnvloedt? Op een platform als Vercel of Cloudflare Pages, niet echt. Deze edge-platforms zijn ontworpen voor verkeerspieken. Statische pagina's worden bediend vanuit CDN-cache, dus een piek op één eigenschap verbruikt geen serverresources die een ander zouden beïnvloeden. Voor dynamische routes (zoals realtime beschikbaarheidscontroles), wil je frequentielimietbeperkingen per eigenschap om te voorkomen dat één eigenschap's viraal moment je boeking engine API-quota uitput. Maar dat is een bezorgdheid op API-niveau, niet op hosting-niveau.