Next.js マルチドメイン & ホスティング運用まとめ
Next.js 13 App Routerでマルチドメインサイトを構築・運用するための包括的ガイド。SSR、ISR、SSGの使い分けから実装時の注意点まで実践的に解説します。
Next.js マルチドメイン & ホスティング運用まとめ
本記事の構成: 1つのNext.jsアプリで複数ドメイン(personal、company、blog)を運用する実践的なガイドです。実装から運用まで包括的に解説しています。
📊 レンダリング方式の理解と現在のトレンド
レンダリング方式比較表
| 方式 | 生成タイミング | 表示速度 | SEO | サーバー負荷 | 適用シーン |
|---|---|---|---|---|---|
| SSG | ビルド時のみ | ⚡⚡⚡ 最高速 | 🥇 優秀 | ゼロ | 固定コンテンツ |
| ISR | ビルド時+定期更新 | ⚡⚡ 高速 | 🥇 優秀 | 低 | 更新のあるコンテンツ |
| SSR | リクエストごと | ⚡ 高速 | 🥇 優秀 | 高 | リアルタイム必須 |
| SPA | クライアント側 | 🐌 低速 | 🥉 困難 | ゼロ | 管理画面・アプリ |
🎯 現在のトレンド:ISRがスタンダードに
結論: Next.js界隈ではISRが実質スタンダードになりつつあります。ただし、ユースケースによってSSGも全然現役です。
📅 歴史的変遷
- 2016-2019 (v1-v8): SSRが中心、
getInitialPropsの時代 - 2019-2020 (v9-v10): SSG登場、
getStaticPropsで静的サイト革命 - 2020-2022 (v9.5-v12): ISR登場、「静的+動的」のハイブリッド時代
- 2022-現在 (v13+): App Router、Server Componentsで新時代
🏗️ 実用的な組み合わせ戦略
サイト特性別の推奨方式
| サイト特性 | 推奨方式 | 理由 | 実装のコツ |
|---|---|---|---|
| 企業サイト | SSG | 固定コンテンツ中心 | revalidateなし |
| 個人ポートフォリオ | SSG | 更新頻度が低い | 同上 |
| ブログ・ニュース | ISR | 定期的な記事更新 | revalidate: 3600 |
| ECサイト | ISR | 在庫・価格変動 | revalidate: 300 |
| ダッシュボード | SSR | リアルタイム必須 | Server Components |
💡 実践的なパターン
✅ 推奨構成 ├── 固定ページ(About、Contact、LP) → SSG ├── 更新ページ(Blog、News、Products) → ISR └── 動的ページ(Admin、Dashboard) → SSR
🌐 マルチドメインの実現方法
🎯 基本戦略
- 1つのNext.jsアプリで複数ドメインを運用
- Middlewareによるrewriteでホストベースのルーティング
- URLはそのまま、内容のみ動的に切り替え
- SEO最適化とパフォーマンスを両立
ディレクトリ構成例(App Router対応)
src/ middleware.ts ← srcディレクトリ内に配置 app/ personal/ ← アンダースコアなし company/ ← プライベートフォルダ扱いを回避 blog/ page.tsx ← Welcomeページ
middleware例(App Router対応版)
import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; export function middleware(req: NextRequest) { const url = req.nextUrl; const host = req.headers.get('x-forwarded-host') || req.headers.get('host') || url.hostname; // ポート番号を除去 const hostWithoutPort = host.split(':')[0]; const isPersonal = hostWithoutPort === 'rkinoshita.com' || hostWithoutPort === 'personal.localhost' || hostWithoutPort.startsWith('personal.'); const isCompany = hostWithoutPort === 'rk-sys.com' || hostWithoutPort === 'company.localhost' || hostWithoutPort.startsWith('company.'); const isBlog = hostWithoutPort === 'blog.rk-step.com' || hostWithoutPort === 'blog.localhost' || hostWithoutPort.startsWith('blog.'); // 重要: App Routerでは_付きフォルダは使用不可 if (isBlog && !url.pathname.startsWith('/blog')) { return NextResponse.rewrite(new URL(`/blog${url.pathname}${url.search}`, url)); } if (isPersonal && !url.pathname.startsWith('/personal')) { return NextResponse.rewrite(new URL(`/personal${url.pathname}${url.search}`, url)); } if (isCompany && !url.pathname.startsWith('/company')) { return NextResponse.rewrite(new URL(`/company${url.pathname}${url.search}`, url)); } return NextResponse.next(); } export const config = { matcher: [ '/((?!api|_next/static|_next/image|favicon.ico|robots.txt|sitemap.xml|assets/).*)', ], };
重要な変更点:
- ポート番号を除去:
host.split(':')[0] - App Router対応:
/_personal→/personal - 開発環境対応:
personal.localhostを追加
🔄 リダイレクト vs リライト
| 項目 | Redirect | Rewrite |
|---|---|---|
| URLバー | 変わる | 変わらない |
| 実際の動作 | ブラウザが再アクセス | サーバー内部で差し替え |
| SEO | 正規URLを明示 | 表のURLそのまま |
| 用途 | URL正規化や恒久移動 | マルチドメイン切り替え |
📦 完全静的エクスポート(Static Export)
概要
Next.jsアプリケーションを完全な静的サイトとして出力し、任意のWebサーバーでホスティング可能にする機能です。
コマンド
next build && next export
出力例
out/ index.html about/index.html contact/index.html
制約事項
getServerSideProps/ Middleware は利用不可- App Routerは制約が多い → 完全静的化はPages Router推奨
- 画像最適化を使用する場合は
next.config.jsに以下を追加:
module.exports = { images: { unoptimized: true } }
適用シーン
- CDN配信での最高速度が必要
- サーバーレス環境での運用コスト削減
- GitHub PagesやNetlifyなどの静的ホスティング利用
🔒 SEO・セキュリティ観点での考慮事項
SEO観点でのレンダリング方式比較
| レンダリング | 初期表示速度 | クローラビリティ | メタタグ制御 | 推奨用途 | SEO評価 |
|---|---|---|---|---|---|
| SSR | ⚡ 高速 | ✅ 優秀 | ✅ 動的 | ニュース、EC | 🥇 最高 |
| ISR | ⚡ 高速 | ✅ 優秀 | ✅ 動的 | ブログ、CMS | 🥇 最高 |
| SSG | ⚡⚡ 最高速 | ✅ 優秀 | ⚠️ 静的 | コーポレート | 🥇 最高 |
| SPA | 🐌 低速 | ❌ 困難 | ⚠️ 制限 | 管理画面 | 🥉 低い |
マルチドメインSEO戦略
✅ 推奨アプローチ
- サブドメイン分離:
blog.example.com,shop.example.com - 専用sitemap.xml: 各ドメインごとに個別生成
- canonical URL: 重複コンテンツ防止
- robots.txt: ドメインごとの適切な設定
⚠️ 注意すべき点
- 重複コンテンツ: 同一コンテンツの複数ドメイン配信は避ける
- 内部リンク: ドメイン間リンクは外部リンク扱い
- ドメインオーソリティ: 分散による権威性の希薄化
セキュリティ観点での実装考慮事項
CSP (Content Security Policy)
// next.config.js での設定例 const nextConfig = { async headers() { return [ { source: '/(.*)', headers: [ { key: 'Content-Security-Policy', value: "default-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline';" } ] } ] } }
CORS設定
// middleware.ts でのドメイン制御例 export function middleware(request: NextRequest) { const response = NextResponse.next() // 許可されたドメインからのリクエストのみ処理 const allowedOrigins = ['rkinoshita.com', 'rk-sys.com', 'blog.rk-step.com'] const origin = request.headers.get('origin') if (origin && allowedOrigins.includes(origin)) { response.headers.set('Access-Control-Allow-Origin', origin) } return response }
セキュリティヘッダー設定
- X-Frame-Options: Clickjacking攻撃防止
- X-Content-Type-Options: MIME型スニッフィング防止
- Referrer-Policy: リファラー情報制御
- Permissions-Policy: ブラウザ機能アクセス制限
📊 マルチドメイン対応一覧表
レンダリング × ホスティング × ルーター対応表
| レンダリング | Vercel | Cloudflare Pages | AWS S3+CloudFront | Router | リダイレクト/リライト | SEO | セキュリティ |
|---|---|---|---|---|---|---|---|
| SSR | ✅ 完全対応 | ✅ Edge対応 | ❌ 不可 | App/Pages | Rewrite推奨 | 🥇 最高 | 🔒 高 |
| ISR | ✅ 完全対応 | ⚠️ 制限あり | ❌ 不可 | App/Pages | Rewrite推奨 | 🥇 最高 | 🔒 高 |
| SSG | ✅ 完全対応 | ✅ 完全対応 | ✅ 完全対応 | App/Pages | Rewrite/Redirect両方 | 🥇 最高 | 🔒🔒 最高 |
| SPA | ✅ 完全対応 | ✅ 完全対応 | ✅ 完全対応 | App/Pages | Redirect推奨 | 🥉 低い | ⚠️ 注意 |
| Static Export | ✅ 対応 | ✅ 対応 | ✅ 最適 | Pages推奨 | CloudFront Functions | 🥈 良い | 🔒🔒 最高 |
マルチドメイン実装方法別対応表
| 実装方法 | Vercel | Cloudflare Pages | AWS S3+CloudFront | 設定場所 | 複雑度 | セキュリティリスク |
|---|---|---|---|---|---|---|
| Middleware Rewrite | ✅ ネイティブ | ✅ Pages Functions | ❌ 不可 | middleware.ts | 低 | 🔒 低リスク |
| Next.js Rewrites | ✅ 対応 | ✅ 対応 | ❌ 不可 | next.config.js | 中 | 🔒 低リスク |
| サブディレクトリ | ✅ 対応 | ✅ 対応 | ✅ 対応 | 各プラットフォーム | 高 | ⚠️ 要注意 |
| Lambda@Edge | ❌ 不要 | ❌ 不要 | ✅ 必須 | CloudFront | 高 | 🔒 設定次第 |
決定フローチャート
マルチドメインサイトを構築したい ↓ [要件を確認] ↓ ┌─ 動的コンテンツが必要? ─┐ ↓ YES ↓ NO [SSR/ISR] [SSG/Static Export] ↓ ↓ 予算は? ホスティング重視? ↓ ↓ 安い → Cloudflare Pages 速度 → Vercel/CloudFlare 高い → Vercel 制御 → AWS S3+CloudFront ↓ ↓ Pages Functions Lambda@Edge or でrewrite ビルド時ディレクトリ分岐
🏢 ホスティングサービス比較
セキュリティ・SEO機能比較
| 機能 | Vercel | Cloudflare Pages | AWS S3+CloudFront |
|---|---|---|---|
| SSL/TLS証明書 | 🔒 自動 | 🔒 自動 | 🔒 ACM連携 |
| DDoS保護 | ✅ 標準 | ✅✅ 強力 | ✅ AWS Shield |
| WAF | ❌ 無し | ✅ 標準 | ✅ AWS WAF |
| カスタムヘッダー | ✅ 対応 | ✅ 対応 | ✅ 対応 |
| IPアクセス制限 | 💰 有料 | ✅ 無料 | ✅ 対応 |
| GDPR対応 | ✅ 対応 | ✅ 対応 | ✅ 対応 |
| サイトマップ生成 | ✅ Next.js | ✅ Next.js | ⚠️ 手動 |
| Analytics | ✅ 標準 | ✅ 標準 | ⚠️ 別途設定 |
セキュリティベストプラクティス
1. 環境変数の管理
// 本番環境でのみ重要な情報を含める const isProduction = process.env.NODE_ENV === 'production' const apiUrl = isProduction ? process.env.PROD_API_URL : process.env.DEV_API_URL
2. ログインページの保護
// middleware.ts でのアクセス制限 import { NextRequest, NextResponse } from 'next/server' export function middleware(request: NextRequest) { const url = request.nextUrl // 管理画面へのアクセス制御 if (url.pathname.startsWith('/admin')) { const authCookie = request.cookies.get('auth-token') if (!authCookie) { return NextResponse.redirect(new URL('/login', request.url)) } } }
3. レート制限の実装
// API Routesでのレート制限例 const rateLimitMap = new Map() export default function handler(req, res) { const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress const now = Date.now() const windowMs = 15 * 60 * 1000 // 15分 const maxRequests = 100 const requests = rateLimitMap.get(ip) || [] const windowStart = now - windowMs const requestsInWindow = requests.filter(time => time > windowStart) if (requestsInWindow.length >= maxRequests) { return res.status(429).json({ error: 'Too many requests' }) } requestsInWindow.push(now) rateLimitMap.set(ip, requestsInWindow) // 実際のAPI処理 }
🔍 各サービスの詳細比較
Vercel
Next.js開発元による最適化されたプラットフォーム
- 対応機能: SSR/ISR/middleware/画像最適化すべて完全対応
- マルチドメイン: ダッシュボードUIで簡単に複数ドメイン割り当て可能
- メリット: セットアップが最短、開発体験が最高
- デメリット: ベンダーロックイン懸念、コストが高め
Cloudflare Pages
エッジ配信とコスト効率に優れる
- 対応機能:
@cloudflare/next-on-pages経由でNext.js Edge機能対応 - マルチドメイン: Pages Functionsでrewrite実装可能
- メリット: コスト効率が良い、Cloudflareエコシステムとの統合性
- デメリット: 一部Next.js機能に制限あり
AWS S3 + CloudFront
大規模運用向けのフルマネージド構成
- 対応機能: 完全静的エクスポート前提
- マルチドメイン: CloudFront Functions/Lambda@EdgeでHost判定→サブディレクトリrewrite
- メリット: 高い可用性、細かい制御が可能
- デメリット: MiddlewareやISR不可、セットアップが複雑
⚠️ 実装時の重要な注意点
App Routerでのディレクトリ命名規則
❌ よくある間違い
src/app/_personal/ ← アンダースコア付き src/app/_company/ ← プライベートフォルダ扱いでルーティング対象外 src/app/_blog/
✅ 正しい実装
src/app/personal/ ← 通常のルートディレクトリ src/app/company/ ← middlewareでアクセス可能 src/app/blog/
理由: Next.js 13のApp Routerでは、_で始まるフォルダはプライベートフォルダとして扱われ、ルーティングから除外される。
middlewareファイルの配置場所
❌ よくある間違い
project/ ├── middleware.ts ← srcディレクトリの外 └── src/ └── app/
✅ 正しい配置
project/ └── src/ ├── middleware.ts ← srcディレクトリの中 └── app/
理由: srcディレクトリを使用している場合、middlewareもsrc内に配置する必要がある。
Turbopackとmiddlewareの相性問題
発生する問題
npm run dev --turbopack # middlewareが実行されない場合がある
解決策
npm run dev # 通常モードで実行
🛠️ よくあるエラーと解決策
ビルド関連
-
sh: next: not found→npm install実行後にnpx next buildを使用 -
Non-standard NODE_ENV →
NODE_ENVはdevelopment/production/testのみ使用可能。ステージング環境の識別は別の環境変数で対応
型エラー関連
- Type error: Property 'title' does not exist → コンポーネントのprops型を明確に定義して渡す
App Router移行関連
- Error:
<Html>should not be imported outside of pages/_document → App Routerでは<html>/<head>を素のHTMLタグとして使用。next/documentは使用不可
レンダリング関連
- Hydration mismatch
→ サーバーサイドとクライアントサイドで異なる内容がレンダリングされている。
useEffectやブラウザ固有のAPIの使用タイミングを見直す
マルチドメイン実装関連
-
middlewareが実行されない → 1)
middleware.tsをsrcディレクトリ内に配置 2) Turbopackを無効化して通常モードで実行 -
404エラー(ページが見つからない) → App Routerで
_付きフォルダは使用不可。src/app/_personal/→src/app/personal/に変更 -
ホスト判定が失敗する → ポート番号を除去:
host.split(':')[0]でホスト名のみ取得
🎯 推奨実装戦略
段階的移行アプローチ
-
現在: Vercelで開発・運用
- middlewareを活用して3サイトを1リポジトリに統合
- 各サイトに適したレンダリング方式を選択(SSG/SSR/ISR)
-
将来の選択肢: 運用要件に応じて選択
- コスト重視: Cloudflare Pagesへの移行を検討
- 大規模運用: AWS S3 + CloudFrontでの静的配信
- 完全ポータブル:
next exportによる完全静的化
🎯 実用的なサイト構成別推奨戦略
あなたのマルチドメイン構成での最適解
| ドメイン | 推奨方式 | 理由 | 更新頻度 | 実装ポイント |
|---|---|---|---|---|
| Personal (rkinoshita.com) | SSG | ほぼ固定コンテンツ | 稀 | revalidateなしでビルド時生成 |
| Company (rk-sys.com) | SSG | 企業情報は安定 | 稀 | 同上、SEO最適化重視 |
| Blog (blog.rk-step.com) | ISR | 記事更新が発生 | 頻繁 | revalidate: 3600(1時間) |
ISRの実装例(ブログ向け)
// app/blog/page.tsx (App Router) export const revalidate = 3600 // 1時間ごとに再生成 export default async function BlogPage() { const posts = await getBlogPosts() // CMSや外部APIから取得 return <BlogList posts={posts} /> } // または Pages Router export async function getStaticProps() { const posts = await getBlogPosts() return { props: { posts }, revalidate: 3600 // 1時間 } }
SSGの実装例(企業・個人サイト向け)
// app/about/page.tsx (App Router) // revalidateの指定なし = SSG export default function AboutPage() { return <AboutSection /> } // または静的データのfetch async function getCompanyInfo() { // ビルド時のみ実行される return await fetch('api/company-info') }
現実的な運用での考慮点
✅ ISRのメリット(ブログに最適)
- 新記事公開後、自動的に一覧ページも更新
- ユーザーは常にSSG並みの速度を体験
- CMS更新 → サイト反映のタイムラグが最小限
✅ SSGのメリット(企業・個人サイトに最適)
- 絶対的な高速表示
- CDN配信コストが最安
- サーバー負荷ゼロ
⚠️ 運用での注意点
- ISRは初回アクセス時に再生成される = 一瞬重い場合がある
- revalidate時間の設定が重要(短すぎるとサーバー負荷、長すぎると更新反映遅延)
- App Routerでは
revalidate指定なし = 自動でSSG扱い
📖 クイックリファレンス
🚀 最速セットアップ(初心者向け)
プラットフォーム: Vercel レンダリング: SSG ルーター: App Router マルチドメイン: Middleware Rewrite
💰 コスト重視
プラットフォーム: Cloudflare Pages レンダリング: SSG または Static Export ルーター: App Router マルチドメイン: Pages Functions
🏗️ エンタープライズ
プラットフォーム: AWS S3 + CloudFront レンダリング: Static Export ルーター: Pages Router マルチドメイン: Lambda@Edge + サブディレクトリ
⚡ パフォーマンス最優先
プラットフォーム: 任意(CDN配信) レンダリング: SSG ルーター: App Router マルチドメイン: ビルド時生成 + Rewrite
🔒 セキュリティ重視
プラットフォーム: AWS S3 + CloudFront + WAF レンダリング: Static Export ルーター: Pages Router セキュリティ: Lambda@Edge + IPアクセス制限
🎯 SEO最優先
プラットフォーム: Vercel (Analytics付き) レンダリング: SSR または ISR ルーター: App Router SEO対策: 自動sitemap + メタタグ最適化
📈 2025年現在の推奨構成(トレンド反映)
プラットフォーム: Vercel 固定ページ: SSG(企業・個人サイト) 動的ページ: ISR(ブログ・ニュース) ルーター: App Router マルチドメイン: Middleware Rewrite
🔮 Next.js レンダリング方式の変遷と今後
📅 歴史的変遷
- 2016-2019 (v1-v8): SSRが中心、
getInitialPropsの時代 - 2019-2020 (v9-v10): SSG登場、
getStaticPropsで静的サイト革命 - 2020-2022 (v9.5-v12): ISR登場、「静的+動的」のハイブリッド時代
- 2022-現在 (v13+): App Router、Server Componentsで新時代
🎯 現在の主流パターン
- 大多数のサイト: ISRをベースに、固定部分はSSG
- 完全静的サイト: SSG +
next export - リアルタイム必須: SSR(管理画面、ダッシュボード)
🔮 今後の予測
- Server Componentsが更に普及 → SSR/ISRの境界が曖昧に
- Edge Computingの進化 → より細かい地域でのキャッシュ制御
- AI/CMS統合が進み → ISRの自動最適化
💡 実践的な選択指針
質問: あなたのサイトは? ├─ 頻繁に更新される? │ ├─ Yes → ISR(revalidate設定) │ └─ No → SSG(最高速&最安) └─ リアルタイムが必須? ├─ Yes → SSR └─ No → ISR or SSG
🎯 まとめ
本記事では、Next.js 13 App Routerでマルチドメインサイトを構築・運用するための包括的な手法を解説しました。
🔑 キーポイント
- レンダリング方式: ISRが現在のスタンダード、固定コンテンツはSSGが最適
- マルチドメイン実装: Middlewareによるrewriteで1アプリ複数ドメイン運用
- 実装注意点:
_付きフォルダは使用不可、middlewareはsrc内に配置 - ホスティング選択: 要件に応じてVercel/Cloudflare Pages/AWS S3を使い分け
🚀 実装の成功要因
- 段階的アプローチ: 小さく始めて徐々に機能拡張
- パフォーマンス重視: 適切なレンダリング方式の選択
- SEO対策: 各ドメインに最適化されたメタデータ設定
- 保守性: 明確なディレクトリ構成と責務分離
このガイドを参考に、効率的で保守しやすいマルチドメインサイトを構築してください!