Skip to content

03 — Multi-Site Setup

Dev Guide — Savoy Signature Hotels
PRD refs: 03_MultiSite_and_Domains.md, 06_Content_Modeling_Umbraco.md, 01_General_Architecture.md


All 8 hotel websites run on a single Umbraco instance with separate content trees. This guide covers root node configuration, domain bindings, shared content, and the site resolution flow from request to rendered page.


#Site KeyDisplay NameSynxis Hotel IDLanguages
1savoy-signatureSavoy SignaturePT, EN
2savoy-palaceSavoy Palace7990PT, EN
3royal-savoyRoyal SavoyTBDPT, EN
4saccharumSaccharumTBDPT, EN
5the-reserveThe ReserveTBDPT, EN
6calheta-beachCalheta BeachTBDPT, EN
7gardensGardensTBDPT, EN
8hotel-nextHotel NextTBDPT, EN

Synxis Chain ID (all sites): 25136

SiteKey TypeScript Type:

export type SiteKey =
| 'savoy-signature' | 'savoy-palace' | 'royal-savoy' | 'saccharum'
| 'the-reserve' | 'calheta-beach' | 'gardens' | 'hotel-next';

Content
├── Savoy Signature (siteRoot)
│ └── Home (homePage)
│ ├── About (contentPage)
│ ├── Accommodation (roomsListPage)
│ │ ├── Deluxe Room (roomDetailPage)
│ │ └── Suite Ocean (roomDetailPage)
│ ├── Dining (diningListPage)
│ │ └── Restaurant X (diningDetailPage)
│ ├── Contact (contactPage)
│ └── ...
├── Savoy Palace (siteRoot)
│ └── Home (homePage)
│ └── ...
├── Royal Savoy (siteRoot)
│ └── ...
├── Saccharum (siteRoot)
│ └── ...
├── The Reserve (siteRoot)
│ └── ...
├── Calheta Beach (siteRoot)
│ └── ...
├── Gardens (siteRoot)
│ └── ...
├── Hotel Next (siteRoot)
│ └── ...
└── Shared Content (siteRoot — special)
├── Footer Links
├── Social Media Links
├── Legal Pages (Privacy, Terms, Cookies)
├── Group-wide Promotions
└── Common Labels/Strings

Rules:

  • Each siteRoot has exactly one homePage child
  • All page types nest under homePage
  • No cross-site content references (except Shared Content)
  • Each site’s media is isolated in its own Media Library folder

Each siteRoot node must be configured with these values:

PropertySavoy Palace Example
siteNameSavoy Palace
siteKeysavoy-palace
themesavoy-palace
synxisHotelId7990
synxisChainId25136
navarinoHotelCode28854
defaultLocalept

The siteKey value must match the key used in proxy.ts, theme CSS files, and the FE SiteConfig interface.


Domain strategy is TBD but three models are supported:

ModelExample
Independent domainssavoypalace.com, royalsavoy.com
Path-based (single domain)savoysignature.com/savoypalacehotel/
CombinationMost on savoysignature.com, Hotel Next on hotelnext.pt

Path Prefixes (reference):

SitePath Prefix
savoy-signature/ (default)
savoy-palace/savoypalacehotel
royal-savoy/royalsavoyhotel
saccharum/saccharumhotel
the-reserve/thereservehotel
calheta-beach/calhetabeachhotel
gardens/gardenshotel
hotel-next/ (on hotelnext.pt)

SiteDEVSTAGE
Savoy Signaturesavoy.dev-signature.wycreative.comsavoy.stage-signature.wycreative.com
Savoy Palacesavoy.dev-palace.wycreative.comsavoy.stage-palace.wycreative.com
Royal Savoysavoy.dev-royal.wycreative.comsavoy.stage-royal.wycreative.com
Saccharumsavoy.dev-saccharum.wycreative.comsavoy.stage-saccharum.wycreative.com
The Reservesavoy.dev-reserve.wycreative.comsavoy.stage-reserve.wycreative.com
Calheta Beachsavoy.dev-calheta.wycreative.comsavoy.stage-calheta.wycreative.com
Gardenssavoy.dev-gardens.wycreative.comsavoy.stage-gardens.wycreative.com
Hotel Nextsavoy.dev-next.wycreative.comsavoy.stage-next.wycreative.com
Umbraco CMSsavoy.dev-cms.wycreative.comsavoy.stage-cms.wycreative.com

301

301

301

savoysignature.com

www.savoysignature.com

hotelnext.pt

www.hotelnext.pt

HTTP

HTTPS


The site resolution happens in proxy.ts (Next.js 16 replaces middleware.ts):

YES

NO

/savoypalacehotel/*

/royalsavoyhotel/*

...

No match

No locale

Valid locale

Incoming Request

[1] Read hostname from request

[2] hostname === 'hotelnext.pt'?

siteKey = 'hotel-next'

[3] Match path prefix (sorted longest-first)

Path match?

siteKey = 'savoy-palace'

siteKey = 'royal-savoy'

...

siteKey = 'savoy-signature' (default)

[4] Inject headers:

x-site-key, x-site-theme, x-site-root-id

[5] Check locale segment

302 redirect to /{defaultLocale}/{path}

Set x-locale header, continue

Matcher pattern: ['/((?!api|_next/static|_next/image|favicon.ico).*)']


export interface SiteConfig {
key: SiteKey;
name: string;
domain: string;
pathPrefix: string;
umbracoRootId: string; // GUID of the siteRoot node
synxisHotelId?: string;
synxisChainId: string;
defaultLocale: string;
supportedLocales: string[];
theme: string;
navarinoHotelCode?: string;
navarinoApiToken?: string;
}

The Shared Content node is a special siteRoot that contains content shared across all 8 sites:

  • Footer Links
  • Social Media Links
  • Legal Pages (Privacy Policy, Terms, Cookies)
  • Group-wide Promotions
  • Common Labels/Strings

Content Inheritance Rules:

  1. Site-specific content ALWAYS overrides Shared Content
  2. If a site doesn’t define a value, fall back to Shared Content
  3. No cross-site references between hotel nodes
  4. Media is organized per-site (no sharing of media assets)

Each site maps to a CSS theme file:

packages/themes/src/
├── _base.css # Shared tokens (all sites)
├── savoy-signature.css # [data-theme="savoy-signature"]
├── savoy-palace.css # [data-theme="savoy-palace"]
├── royal-savoy.css
├── saccharum.css
├── the-reserve.css
├── calheta-beach.css
├── gardens.css
└── hotel-next.css

The root layout reads x-site-key from headers and sets:

<html lang="{locale}" data-theme="{siteKey}">
<body class="site-{siteKey}">

SettingValue
SSL/TLSFull (Strict)
Always HTTPSOn
Minimum TLS Version1.2
HTTP/2On
HTTP/3 (QUIC)On
BrotliOn
Auto MinifyHTML, CSS, JS

Cache is per hostname+path. Shared Content changes trigger purge across ALL sites.


  1. Create siteRoot node in Umbraco with all required properties
  2. Create homePage child under the new siteRoot
  3. Configure domain binding in Umbraco (Culture & Hostnames)
  4. Add theme CSS file in packages/themes/src/{site-key}.css
  5. Add SiteConfig entry in the frontend site configuration
  6. Update proxy.ts with the new hostname/path prefix mapping
  7. Configure Cloudflare DNS for the new domain
  8. Create Media Library folders for the new site
  9. Set up RBAC — create editor group scoped to the new site’s content tree
  10. Test — Verify API returns content scoped to the new site only

PitfallSolution
siteKey mismatch between Umbraco and proxy.tsUse identical values everywhere: Umbraco, proxy.ts, SiteConfig, theme file
Missing Start-Item header in API callsAlways pass siteKey as Start-Item to scope queries
Shared Content changes don’t reflectShared Content publish must trigger cache purge for ALL 8 sites
Editor accessing wrong site’s contentConfigure RBAC — scope editor groups to specific root nodes
Path prefix collisionSort path prefixes longest-first in resolution logic
Forgot to create Media Library foldersPre-create per-site folders before editors start uploading