🐡

Next.jsで `--experimental-https` フラグをつけて開発するときに気をつけたいこと

2024/11/08に公開

ハマったので共有します。

--experimental-httpsフラグとは

nextjsをhttpsで立ち上げることができる。利用している認証サービスがhttpsを要求するときに、便利。

next dev --experimental-https

こちらの記事が詳しい。
https://zenn.dev/ikuma/articles/how-next-do-https-at-local

問題点

httpなバックエンドサーバーと通信するときにエラーが発生する。

Access to fetch at 'http://localhost:3000/api/xxx' from origin 'https://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: Redirect is not allowed for a preflight request.

当初、バックエンドサーバー側のCORSの設定かと思ったが違った。ブラウザの仕様の問題だった。
流れ:

  • ブラウザがhttpsアプリケーションからhttpリクエストが発火すると、httpsにリダイレクトしようとする
  • preflight reqestはリダイレクトできない
  • 上記のエラーが発生

ブラウザがhttpをhttpsにリダイレクトさせるのを止めるのは難しそうなため、バックエンドサーバー側もhttps化すればよい。

バックエンドサーバーをhttps化する

next.jsを--experimental-httpsフラグで立ち上げると、自己署名証明書まわりをよしなにやってくれる。今回はその設定を利用してバックエンド側もその署名を指定することで、https化する。

流れ:

  1. バックエンドサーバーのレポジトリにシェルスクリプトadd-cert-envを作成
    • CERT_DIRにnext dev --experimental-httpsで指定されたファイルに書き換える
    • すると、証明書をbase64エンコードされたものが、'.env'に追加される
  2. シェルスクリプトadd-cert-envを実行
  3. バックエンドサーバー側で、証明書を書き換え
  4. Next.jsからhttpsでリクエストできるようになる
add-cert-env
#!/bin/bash

# 証明書と秘密鍵のあるディレクトリを指定
# これは next dev --experimental-httpsによって生成されたものを指定する
CERT_DIR="../web/certificates"

# もし該当ファイルがなければエラーを出力して終了
if [ ! -f $CERT_DIR/localhost-key.pem ] || [ ! -f $CERT_DIR/localhost.pem ]; then
  echo "証明書または秘密鍵が見つかりません。先にpnpm web devを実行して証明書を生成してください。"
  exit 1
fi

# base64エンコードして環境変数として`.env`ファイルに追加
echo "SSL_KEY=$(cat $CERT_DIR/localhost-key.pem | base64)" >> .env
echo "SSL_CERT=$(cat $CERT_DIR/localhost.pem | base64)" >> .env

# 完了メッセージ
echo ".env ファイルに SSL_KEY と SSL_CERT が追加されました。"
main.ts
async function bootstrap() {
  if (process.env.NODE_ENV === 'development') {
    if (!process.env.SSL_KEY || !process.env.SSL_CERT) {
      throw new Error(
        'pnpm api add-cert-envで自己署名証明書を作成してください',
      );
    }
  }
  const httpsOptions =
    process.env.NODE_ENV === 'development'
      ? {
          key: Buffer.from(process.env.SSL_KEY!, 'base64'),
          cert: Buffer.from(process.env.SSL_CERT!, 'base64'),
        }
      : undefined;

  const app = await NestFactory.create(AppModule, {
    httpsOptions,
  });

  app.enableCors({
    origin: process.env.CORS_URL ? process.env.CORS_URL.split(',') : undefined,
    allowedHeaders:
      'Origin, X-Requested-With, Content-Type, Accept, Authorization',
    credentials: true,
  });

  await app.listen(process.env.PORT || 3000);
}
bootstrap();
TRAPE(トラピ)

Discussion