Next.js の静的エクスポートでマルチドメインルーティングがうまくいかない話


はじめに

Next.js でマルチドメインサイトを作ろうとして、静的エクスポート(output: 'export')を使ったところ、ハマった。

やりたかったこととしては、同じプロジェクト内で example.comexample.jp で内容が違うコンテンツをホストしたかった。(左記の例で言えば、 example.com では英語版ページ, example.jp では日本語版ページを表示するなど。)

vercel.json をいじったりしても一向に思い通りに動かない。結論から言うと、静的エクスポートを使っているのが原因だった。

何が起きたか

挙動としては以下のような感じ。

  • https://example.jp/example.jp コンテンツが表示される
  • https://example.jp/ 空のホームが表示される

まぁ、プロジェクト構成だけ見れば、↑の状態で正しいが、 rewrites を指定しているのに意図したリライトが行われていなかった。

プロジェクトの構成

pages/
  [domain].tsx # 動的なページ
  index.tsx
data/
  domain.ts    # ドメイン別に表示したい内容
next.config.ts
vercel.json    # Vercel のデプロイ設定

domain.ts は以下のような単純な構成とする。

export const domainData = {
    'example.com': {
        'title': 'Welcome',
        'content': 'Welcome to example.com',
    },
    'example.jp': {
        'title': 'ようこそ',
        'content': 'example.jp へようこそ',
    },
}

最初は vercel.json の設定が悪いのかと思い、以下のような rewrites を書いていた。

{
  "rewrites": [
    {
      "source": "/",
      "has": [
        {
          "type": "host",
          "value": "example.jp"
        }
      ],
      "destination": "/example.jp/index.html"
    },
    {
      "source": "/",
      "has": [
        {
          "type": "host",
          "value": "example.com"
        }
      ],
      "destination": "/example.com/index.html"
    }
  ]
}

しかし、これでも解決しない。

原因

静的エクスポートでは、ビルド時に HTML ファイルの内容が固定されてしまうため、ミドルウェアでリクエスト時にホストヘッダーを見てコンテンツを動的に変更することができないと考えられる。まぁそれでも Vercel 側のリライト設定が思ったとおり動いていないのが謎だが…。

https://example.jp/example.jp が動作するのは、getStaticPaths でビルド時にそのパスの静的ファイルが生成されているから。しかし、ルートパス(/)にはドメイン固有のコンテンツがない。

(それはそうだ。)

解決方法

最終的に、静的エクスポートをやめて Edge Middleware を使うことで解決した。

next.config.js から output: 'export' を削除し、 middleware.[ts|js] を実装する。

簡単に書くと↓のような感じ。

import { NextResponse } from 'next/server';

export function middleware(request) {
  const host = request.headers.get('host');
  const pathname = request.nextUrl.pathname;
  
  const domainMappings = {
    'example.jp': 'example.jp',
    'example.com': 'example.com',
  };
  
  const domainKey = domainMappings[host];
  
  if (domainKey) {
    if (pathname === '/') {
      return NextResponse.rewrite(
        new URL(`/${domainKey}`, request.url)
      );
    }
    
    if (!pathname.startsWith(`/${domainKey}`)) {
      return NextResponse.rewrite(
        new URL(`/${domainKey}${pathname}`, request.url)
      );
    }
  }
  
  return NextResponse.next();
}

// ....

このミドルウェアを作ることで、実質的に vercel.json が不要になる。

まとめとか

どうしても静的エクスポートを使いたい場合は、クライアントサイドでドメインを検出しコンテンツを組み立てることも出来るが、SEO上の懸念などが生まれる。

やや不完全燃焼気味だが Next.js でマルチドメインサイトを作るなら、静的エクスポートは避けた方が良い。


comments powered by Disqus