ホテルグループウェブサイト:Next.jsを使用したマルチプロパティアーキテクチャ
ホテルグループのウェブサイト管理は簡単です。30軒のホテルを管理するとなると、ほとんどのチームが何年も後悔することになる決断を下すようになります。私は、ホテルグループが物件ごとに個別のWordPressインストールを寄せ集めたり、一枚岩のCMSプラットフォームにページビルダーを無理やり張り付けたり、新しい物件のローンチに3か月以上かかる企業向けソリューションに6桁の予算を燃やすのを見てきました。
もっと良い方法があります。適切に構成された単一のNext.jsアプリケーションは、ホテルグループのすべての物件を1つのコードベース、1つのデプロイメントパイプライン、1つのコンテンツ管理レイヤーから提供できます。各物件は独自のブランディング、独自のコンテンツ、独自のドメインを持ちます。エンジニアリングチームは正気を取り戻します。
この記事では、そのシステムを構築する方法を詳細に説明します。理論ではなく、実際のホテルグループプロジェクトで使用した実際のアーキテクチャパターンです。
目次
- ホテルグループが統一プラットフォームを必要とする理由
- アーキテクチャ概要: 1つのコードベース、複数の物件
- Next.jsのマルチテナンシーパターン
- ホテルグループ向けのヘッドレスCMS戦略
- 共有コンポーネント対物件レベルのカスタマイズ
- 予約エンジン統合
- ドメインルーティングと物件解決
- スケール時のパフォーマンス
- 一元管理ダッシュボード
- デプロイメントとDevOps
- 実世界のコスト比較
- FAQ

ホテルグループが統一プラットフォームを必要とする理由
一般的なホテルグループのウェブサイト状況は次のようなものです: 物件AはWordPressで2019年のテーマを実行しています。物件BはGMの甥がセットアップしたのでSquarespaceを使用しています。物件Cは誰もいじりたくない昔のカスタムPHPサイトです。企業サイトはまったく別のプラットフォームにあります。
すべての物件の更新には異なるワークフローが必要です。ブランド統一性はパイプの夢です。SEO戦略は共有権限のない数十のドメインに断片化されています。企業が新しいアメニティバッジを追加したり予約ウィジェットを更新することを決定すると、誰かがそれを15の異なる場所で変更する必要があります。
コストは複合的に増加します:
- メンテナンスオーバーヘッド: 各プラットフォームは独自のホスティング、セキュリティパッチ、プラグイン更新が必要です
- ブランドドリフト: 物件は徐々にブランドガイドラインから逸脱していきます
- 開発者の状況切り替え: チーム (またはエージェンシー) は複数のプラットフォーム全体にわたる専門知識が必要です
- 遅い物件ローンチ: 新しい買収がオンラインになるのに数か月かかります
- 分析の断片化: ポートフォリオ全体のパフォーマンスの統一ビューがありません
一元管理マルチ物件プラットフォームはすべてを解決します。1つのコードベース。1つのデプロイメント。1つのCMS。物件ごとのコンテンツとブランディングは個別のコードベースではなく設定で提供されます。
アーキテクチャ概要: 1つのコードベース、複数の物件
ここが機能するハイレベルアーキテクチャです:
┌─────────────────────────────────────────────┐
│ 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 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────┘
Next.jsアプリはレンダリングレイヤーとして機能します。ミドルウェアは要求されている物件を決定します (ドメイン、サブドメイン、またはパス経由)。テーマエンジンは物件固有のスタイルを適用します。コンテンツフェッチャーはヘッドレスCMSから物件スコープのコンテンツを取得します。
すべてのダウンストリーム — CMS、予約エンジン、メディアストレージ — は物件識別子でクエリされます。その識別子はシステム全体を結びつけるスレッドです。
Next.jsのマルチテナンシーパターン
Next.jsのマルチテナンシーには3つの主要なアプローチがあります。それぞれにトレードオフがあります。
パターン1: サブドメインベースのルーティング
各物件はサブドメインを取得します: grandplaza.hotelgroup.com, seasideresort.hotelgroup.com.
Next.jsミドルウェアは要求をインターセプトし、サブドメインを抽出して物件設定を解決します:
// 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;
}
メリット: クリーンなURL、簡単な物件隔離、物件が個別のTLDを必要としない場合のSEOが良い。
デメリット: SSL証明書管理にはワイルドカード、物件ごとのブランド独立性が低い。
パターン2: カスタムドメインマッピング
各物件は独自のドメインを持ちます: grandplazahotel.com, seasideresort.com.
これはほとんどのホテルグループが実際に望んでいるものです。ミドルウェアロジックは似ていますが、ドメインルックアップテーブルに対してマッチングしています:
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はプロジェクトごとのカスタムドメインをネイティブでサポートしており、Pro プラン ($20/月は2025年現在) で最大50のドメインをマッピングできます。より大きなポートフォリオの場合、Enterprise プランはその制限を削除します。
メリット: 完全なブランド独立性、既存のドメイン権を保持。
デメリット: DNS管理オーバーヘッド、より複雑なSSLプロビジョニング。
パターン3: パスベースのルーティング
1つのドメインの下のすべての物件: hotelgroup.com/properties/grand-plaza, hotelgroup.com/properties/seaside-resort.
メリット: 実装が最も簡単、SEOの統合ドメイン権。
デメリット: 物件ごとのブランド アイデンティティが低い、URL構造が企業的に感じる。
| パターン | ブランド独立性 | SEO柔軟性 | 実装複雑性 | 最適な用途 |
|---|---|---|---|---|
| サブドメイン | 中程度 | 中程度 | 低い | 予算に配慮するグループ |
| カスタムドメイン | 高い | 高い | 中程度 | 確立されたブランド |
| パスベース | 低い | 高い (統合) | 最低 | 新しいポートフォリオサイト |
Social Animalと協力しているほとんどのホテルグループは最終的にカスタムドメインマッピングを選択しています。物件はドメインにブランド権を持っており、マーケティングチームは独立性を望んでいます。

ホテルグループ向けのヘッドレスCMS戦略
CMS選択はこのアーキテクチャの成功を左右します。コンテンツレベルでマルチテナンシーをサポートするシステムが必要です — 物件Aの編集者が誤って物件Bのコンテンツを変更できないが、企業の管理者はすべてを管理できる場合です。
適切に機能するCMSオプション
Sanityはホテルグループの私の第一選択肢です。ドキュメントレベルのアクセス権限、カスタムスタジオ構成、GROQクエリ言語により、物件スコープのコンテンツ取得が簡単です。ワークスペースごとの物件ビューを使用して単一のSanity Studioを構築できます。価格はTeamプラン $99/月から始まります (2025年の価格)。大規模なコンテンツボリュームに対応しています。
Contentfulは既にエコシステムにいる場合に機能します。スペースレベルの隔離は物件に適切にマッピングされますが、料金が増加する可能性があります — Premiumプランの各スペースはコストを追加し、企業規模のホテルグループニーズでは$2,500+/月を見ています。
Strapi (self-hosted) は予算オプションです。カスタムミドルウェアとロールベースのアクセス制御を使用してマルチテナンシーレイヤー自体を構築する必要がありますが、シート単位のライセンスコストはありません。
完全なCMS選択プロセスについては、ヘッドレスCMS開発ガイドで説明しています。
ホテル向けのコンテンツモデリング
物件全体で機能するコンテンツモデルは次の通りです:
// 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' }] }),
],
});
重要なパターン: すべてのコンテンツドキュメントはpropertyを参照します。クエリは常に物件でフィルタリングされます。編集者は自分の物件のコンテンツのみが表示されます。企業管理者はすべてを表示できます。
共有コンポーネント対物件レベルのカスタマイズ
ここがアーキテクチャが興味深くなる場所です。物件全体で80%のコンポーネントを共有したいが、20%は物件ごとのカスタマイズを可能にしたいです。
テーマレイヤー
物件ごとのテーマ構成を作成して、コンポーネントシステムに供給します:
// 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 (2025年リリース) はCSS優先の設定とネイティブテーム関数サポートにより、これを大幅に簡単にしています。レイアウトレベルでCSS カスタムプロパティを設定でき、すべてのコンポーネントを通じてカスケードします:
// 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>
);
}
コンポーネント構成
共有コンポーネントはテーマトークンを受け入れ、ブランチロジックなしで物件ごとに異なるレンダリングを行います:
// 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>
);
}
予約エンジン統合
ホテルのウェブサイトは1つの理由で存在します: 予約を促進するためです。予約エンジンの統合は非常に堅牢である必要があります。
ほとんどのホテルグループはこれらの予約エンジンの1つを使用します: SynXis (Sabre), Pegasus, Bookassist, SiteMinder、またはプロプライエタリな一元予約システム。統合パターンはほぼ常に同じです: 物件識別子、日付範囲、ゲスト数を渡して可用性を取得します。
// 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();
}
予約ウィジェット自体では、2つのオプションがあります:
- 埋め込みiframe: 予約エンジンはウィジェットを提供して埋め込みます。最も少ないの作業、最小限の制御。
- API駆動カスタムUI: 検索と結果UIを構築し、予約APIを直接呼び出し、支払いのみの予約エンジンに引き渡します。より多くの作業、はるかに優れたUX。
オプション2はNext.jsアーキテクチャが本当に輝く場所です。美しく、高速で、ブランドに沿った予約体験を構築でき、各物件に本来的に感じます。Server Componentsは可用性データを事前取得できます。予約フローはドメイン上に留まり、これはコンバージョン追跡とSEOに適しています。
ドメインルーティングと物件解決
物件解決フローは高速である必要があります。本当に高速です。すべてのリクエストで実行されます。
本番環境で機能するパターンは次の通りです:
- Edgeミドルウェアはドメイン → 物件スラッグを解決します (メモリ内ルックアップ、サブミリ秒)
- 物件設定はVercel Edge ConfigまたはCloudflare KVを使用してエッジでキャッシュされます
- 完全な物件データ (テーマ、ナビゲーション、フッターコンテンツ) は、ISRやリクエスト時のキャッシングを使用してビルドごとに1回フェッチされます
// 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はこれに最適です — グローバルに分散されたキー値ストアで、読み取り遅延は1ミリ秒未満です。Pro プランでは最大512KBのデータについて $0がかかり、これは物件ルックアップテーブルには十分です。
スケール時のパフォーマンス
ホテルのウェブサイトには、重要な特定のパフォーマンス特性があります:
- 画像の多いページ: ルームギャラリー、物件写真、デスティネーション画像
- 季節的なトラフィックスパイク: 休日、コンベンションシーズン、ローカルイベント
- グローバルオーディエンス: 世界中からのインターネットユーザーブラウジング
- コンバージョン重要: 読み込み時間が100ミリ秒増加するとコンバージョンが失われます
静的生成戦略
物件ページのIncremental Static Regeneration (ISR) を使用します。ホテルコンテンツは毎分変わりません — 60秒の再検証期間は通常は問題ありません:
// app/[propertySlug]/page.tsx
export async function generateStaticParams() {
const properties = await getAllProperties();
return properties.map((p) => ({ propertySlug: p.slug }));
}
export const revalidate = 60;
30物件のグループで物件あたり約20ページの場合、約600ページを事前生成しています。Next.jsはこれを問題なく処理します。ビルド時間は5分以下です。
画像最適化
Next.js Image コンポーネントとリモートローダーが物件ごとの画像最適化を処理します。Sanityを使用している場合、自動形式変換とリサイズを使用した画像CDNが優れています。Cloudinaryは別の堅牢なオプションで、Plus プラン $89/月です。
一般的なホテル物件ページの目標:
- 4G接続での2.5秒未満のLCP
- 0のCLS (画像読み込みからのレイアウトシフトなし)
- 初期読み込みで1.5MB未満の総ページ重量
一元管理ダッシュボード
CMSを超えて、ホテルグループは運用ダッシュボードが必要です。これはカスタムツール構築の場所です:
- 物件概要: 各物件サイトのステータス (ライブ、ステージング、メンテナンス)
- コンテンツの鮮度: どの物件が季節コンテンツを更新していないか
- パフォーマンス監視: 物件ごとのCore Web Vitals
- 分析ロールアップ: すべての物件でのブッキングファネルメトリクス
通常、これを別のNext.jsアプリ (多くの場合、App Routerのサーバー側機能を使用) として構築し、同じデータソースに接続します。管理ダッシュボードは内部ツール — 輝く必要はありませんが、機能的である必要があります。
デプロイメントとDevOps
1つのコードベースは1つのCI/CDパイプラインを意味します。デプロイメントフローは次の通りです:
- コード変更: PR → レビュー → mainにマージ
- ビルド: Next.jsはすべての物件全体の静的ページをビルドします
- デプロイ: Vercel (または同様) はエッジネットワークにデプロイします
- DNS: 各物件ドメインはデプロイメントをポイントします
コンテンツ変更はデプロイメントを必要としません。ヘッドレスCMSはWebhookを使用してISR再検証をトリガーします:
// 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 });
}
実世界のコスト比較
20物件ホテルグループの実際のコストを比較してみましょう:
| コストカテゴリー | 個別サイト (WordPress) | 統一Next.jsプラットフォーム |
|---|---|---|
| ホスティング (月次) | $2,000-4,000 (20 × managed WP) | $150-400 (Vercel Pro/Team) |
| CMSライセンス | $0-600 (サイトごとのプラグイン) | $99-300 (Sanity/Contentful) |
| SSL証明書 | $0-400 (Let's Encryptを使用しない場合) | $0 (自動プロビジョニング) |
| メンテナンス (年次) | $40,000-80,000 (更新、セキュリティ) | $10,000-20,000 |
| 新しい物件ローンチ | $5,000-15,000 per site | $500-2,000 (コンテンツ + config) |
| 年次合計 (推定) | $75,000-150,000 | $15,000-35,000 |
数字は全く相比にはなりません。そして、これはデベロッパー体験の向上を考慮していません — 1つのコードベースを持つことはチームが実際にシステムを理解していることを意味します。「その物件はどのWordPressバージョンを実行していますか?」はもうありません。
このアプローチを検討しているホテルグループの場合、Next.js開発機能の概要を作成し、より詳細な見積もりについて価格構造を確認できます。
FAQ
ホテルグループを統一Next.jsプラットフォームに移行するのにどのくらい時間がかかりますか? 10-20物件のグループの場合、キックオフから完全なローンチまで3-5か月を予期します。最初の物件は最も時間がかかります (8-10週間)、プラットフォームを構築しているためです。その後の各物件は主にコンテンツ移行とテーマ設定で、1-2週間かかります。通常、波で起動しています — 一度に3-4の物件。
個々の物件はまだ他の物件が持たないユニークなページを持つことができますか? もちろんです。コンテンツモデルは物件固有のページタイプをサポートします。リゾート物件が「Wedding Venues」セクションが必要だがビジネスホテルが必要ない場合、それはコンテンツレベルの決定です。CMSスキーマはオプションのページタイプをサポートし、Next.jsの動的ルーティングは特定の物件のCMSに存在するページをレンダリングします。
新しいホテルを買収してプラットフォームに追加する必要がある場合、何が起こりますか? これは最大の勝利の1つです。新しい物件を追加するには次のことが必要です: CMSで物件エントリを作成し、テーマを設定 (色、フォント、ロゴ)、ドメインマッピングを追加、コンテンツを入力します。有能なコンテンツチームは1-2週間で新しい物件をライブできます。独立したウェブサイトを構築するのに2-3か月と比較します。
異なる国の異なる物件で複数言語サポートを処理しますか? Next.jsには組み込みのi18nルーティングサポートがあります。ローカライズされたコンテンツをサポートするヘッドレスCMS (SanityとContentfulの両方) と組み合わせて、各物件をその関連言語で提供できます。バルセロナの物件はスペイン語、カタロニア語、英語、フランス語が必要になる場合があります。マイアミの物件は英語とスペイン語のみ必要になる場合があります。各物件の言語設定は独立しています。
このアーキテクチャはNext.jsではなくAstroで機能しますか? はい、一部のホテルグループではそれが実際により良い選択肢です。物件が主にコンテンツ駆動で最小限のインタラクティビティ (複雑な予約フローなし、など) の場合、AstroのマルチページアーキテクチャはJavaScriptをより少なくしてさらに優れたパフォーマンスを提供できます。マルチテナンシーパターンは似ています — ミドルウェアベースの物件解決、物件スコープを持つヘッドレスCMS、物件ごとのテーマトークン。
物件が個別のドメイン上にあるが単一のアプリケーションから提供される場合、SEOはどのように処理されますか? 各物件ドメインは独自のサイトマップ、独自のrobots.txt、独自の構造化データ (ホテルスキーママークアップ)、独自のメタタグを取得します。Googleの視点からは、これらは完全に個別のウェブサイトです。標準URLは各物件の独自のドメインをポイントします。一元管理スキーママークアップ生成の利点も取得します — すべての物件は自動的にホテル、ルーム、レビュー、ローカルビジネス情報の適切なJSON-LD を取得します。
スパ予約やローカルアクティビティ予約システムなどの物件固有の統合についてはどうですか? コンポーネントアーキテクチャは物件レベルの統合設定をサポートします。CMSの各物件設定は、使用するサードパーティ統合を指定できます。レンダリングレイヤーは条件付きでこれらの統合コンポーネントを含めます。スパ物件はスパ予約ウィジェットを取得します。ダウンタウンのビジネスホテルは会議室コンフィギュレータを取得します。これらは動的インポートとしてロードされるため、それらを使用しない物件のバンドルサイズには影響しません。
1つの物件のトラフィックスパイクが他の物件に影響するリスクはありますか? VercelまたはCloudflare Pagesなどのプラットフォームでは、実際ではありません。これらのエッジプラットフォームはトラフィックスパイク用に設計されています。静的ページはCDNキャッシュから提供されるため、1つの物件のスパイクは別の物件に影響するサーバーリソースを消費しません。動的ルート (リアルタイム可用性チェックなど) の場合、物件ごとの速度制限を行い、1つの物件のウイルスモーメントが予約エンジンAPI割り当てを枯渇させることを防ぎたいでしょう。しかし、それはホスティングの懸念ではなく、APIレベルの懸念です。