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
1. Overview
Section titled “1. Overview”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.
2. The 8 Sites
Section titled “2. The 8 Sites”| # | Site Key | Display Name | Synxis Hotel ID | Languages |
|---|---|---|---|---|
| 1 | savoy-signature | Savoy Signature | — | PT, EN |
| 2 | savoy-palace | Savoy Palace | 7990 | PT, EN |
| 3 | royal-savoy | Royal Savoy | TBD | PT, EN |
| 4 | saccharum | Saccharum | TBD | PT, EN |
| 5 | the-reserve | The Reserve | TBD | PT, EN |
| 6 | calheta-beach | Calheta Beach | TBD | PT, EN |
| 7 | gardens | Gardens | TBD | PT, EN |
| 8 | hotel-next | Hotel Next | TBD | PT, 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';3. Umbraco Content Tree Structure
Section titled “3. Umbraco Content Tree Structure”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/StringsRules:
- Each
siteRoothas exactly onehomePagechild - 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
4. siteRoot Configuration
Section titled “4. siteRoot Configuration”Each siteRoot node must be configured with these values:
| Property | Savoy Palace Example |
|---|---|
siteName | Savoy Palace |
siteKey | savoy-palace |
theme | savoy-palace |
synxisHotelId | 7990 |
synxisChainId | 25136 |
navarinoHotelCode | 28854 |
defaultLocale | pt |
The siteKey value must match the key used in proxy.ts, theme CSS files, and the FE SiteConfig interface.
5. Domain Strategy
Section titled “5. Domain Strategy”Domain strategy is TBD but three models are supported:
| Model | Example |
|---|---|
| Independent domains | savoypalace.com, royalsavoy.com |
| Path-based (single domain) | savoysignature.com/savoypalacehotel/ |
| Combination | Most on savoysignature.com, Hotel Next on hotelnext.pt |
Path Prefixes (reference):
| Site | Path 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) |
6. Domain Bindings per Environment
Section titled “6. Domain Bindings per Environment”DEV/STAGE Domains
Section titled “DEV/STAGE Domains”| Site | DEV | STAGE |
|---|---|---|
| Savoy Signature | savoy.dev-signature.wycreative.com | savoy.stage-signature.wycreative.com |
| Savoy Palace | savoy.dev-palace.wycreative.com | savoy.stage-palace.wycreative.com |
| Royal Savoy | savoy.dev-royal.wycreative.com | savoy.stage-royal.wycreative.com |
| Saccharum | savoy.dev-saccharum.wycreative.com | savoy.stage-saccharum.wycreative.com |
| The Reserve | savoy.dev-reserve.wycreative.com | savoy.stage-reserve.wycreative.com |
| Calheta Beach | savoy.dev-calheta.wycreative.com | savoy.stage-calheta.wycreative.com |
| Gardens | savoy.dev-gardens.wycreative.com | savoy.stage-gardens.wycreative.com |
| Hotel Next | savoy.dev-next.wycreative.com | savoy.stage-next.wycreative.com |
| Umbraco CMS | savoy.dev-cms.wycreative.com | savoy.stage-cms.wycreative.com |
Redirects
Section titled “Redirects”7. Site Resolution Flow
Section titled “7. Site Resolution Flow”The site resolution happens in proxy.ts (Next.js 16 replaces middleware.ts):
Matcher pattern: ['/((?!api|_next/static|_next/image|favicon.ico).*)']
8. SiteConfig Interface
Section titled “8. SiteConfig Interface”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;}9. Shared Content
Section titled “9. Shared Content”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:
- Site-specific content ALWAYS overrides Shared Content
- If a site doesn’t define a value, fall back to Shared Content
- No cross-site references between hotel nodes
- Media is organized per-site (no sharing of media assets)
10. Theme Integration
Section titled “10. Theme Integration”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.cssThe root layout reads x-site-key from headers and sets:
<html lang="{locale}" data-theme="{siteKey}"><body class="site-{siteKey}">11. Cloudflare Zone Configuration
Section titled “11. Cloudflare Zone Configuration”| Setting | Value |
|---|---|
| SSL/TLS | Full (Strict) |
| Always HTTPS | On |
| Minimum TLS Version | 1.2 |
| HTTP/2 | On |
| HTTP/3 (QUIC) | On |
| Brotli | On |
| Auto Minify | HTML, CSS, JS |
Cache is per hostname+path. Shared Content changes trigger purge across ALL sites.
12. Step-by-Step: Adding a New Site
Section titled “12. Step-by-Step: Adding a New Site”- Create
siteRootnode in Umbraco with all required properties - Create
homePagechild under the newsiteRoot - Configure domain binding in Umbraco (Culture & Hostnames)
- Add theme CSS file in
packages/themes/src/{site-key}.css - Add SiteConfig entry in the frontend site configuration
- Update
proxy.tswith the new hostname/path prefix mapping - Configure Cloudflare DNS for the new domain
- Create Media Library folders for the new site
- Set up RBAC — create editor group scoped to the new site’s content tree
- Test — Verify API returns content scoped to the new site only
13. Common Pitfalls
Section titled “13. Common Pitfalls”| Pitfall | Solution |
|---|---|
siteKey mismatch between Umbraco and proxy.ts | Use identical values everywhere: Umbraco, proxy.ts, SiteConfig, theme file |
Missing Start-Item header in API calls | Always pass siteKey as Start-Item to scope queries |
| Shared Content changes don’t reflect | Shared Content publish must trigger cache purge for ALL 8 sites |
| Editor accessing wrong site’s content | Configure RBAC — scope editor groups to specific root nodes |
| Path prefix collision | Sort path prefixes longest-first in resolution logic |
| Forgot to create Media Library folders | Pre-create per-site folders before editors start uploading |