デザインについての学習メモブログ

Next.js入門 #17 実践編 環境ごとのデプロイ戦略 — ステージングと本番を分ける

記事内に広告が含まれています。

Next.js入門 #17 実践編 環境ごとのデプロイ戦略 — ステージングと本番を分ける

#16までで、機能・品質・パフォーマンスの整ったミニブログが完成しました。

しかし、これを実際に運用していく上で、まだ大事な仕組みが1つ欠けています。

「テストが通ったコードだけを、安全に本番へ届ける仕組み」です。

今回は、Vercelのプレビュー環境を活用したステージング運用と、#15で書いたテストをGitHub Actionsで自動実行してからデプロイするCI/CDパイプラインを構築します。

この記事は#15のテスト#8のVercelへのデプロイの知識が前提です。

なぜ環境を分ける必要があるのか

個人開発の初期段階では、「mainブランチにpushしたら即本番反映」で問題ありません。

しかし、次のような状況になると、本番環境に直接pushするのは危険になってきます。

  • 新機能を試したいが、既存ユーザーには見せたくない
  • チームで開発しており、他の人の変更と自分の変更が衝突していないか確認したい
  • リリース前に、実際の本番相当の環境で最終確認をしたい

これらを解決するのが、開発環境・プレビュー(ステージング)環境・本番環境という3段階の構成です。

環境役割URL例
開発(Development)手元のPCで動かすlocalhost:3000
プレビュー(Preview)プルリクエストごとに自動生成される、確認用の環境my-blog-git-feature-x.vercel.app
本番(Production)実際のユーザーがアクセスする環境my-blog.vercel.app

Vercelのプレビュー環境を理解する

Vercelは、GitHubリポジトリと連携している場合、プルリクエストを作成するたびに自動でプレビュー環境をデプロイしてくれます。

追加の設定はほとんど必要ありません。

  1. feature/add-commentのようなブランチを作成し、変更をpush
  2. GitHub上でプルリクエストを作成
  3. Vercelが自動的にビルド・デプロイを実行し、プルリクエストのコメント欄にプレビューURLが表示される

このプレビューURLは、本番とは完全に独立した環境です。

ここで自由に動作確認を行い、問題なければmainブランチにマージする、という流れになります。

Bash
 ローカル開発(Development環境)
   コードを書く
        
        
 プルリクエスト作成
   featureブランチをpush
        
        ├──────────────┬──────────────┐
                                     
 Vercel Preview環境          GitHub Actions
   自動でデプロイ                テストを自動実行
                                     
        └──────────────┬──────────────┘
                       
         レビュー完了 + CIが緑(両方そろって初めて)
              mainへマージ
                       
                       
         Vercel Production環境
              本番へ自動デプロイ

環境ごとに環境変数を使い分ける

#9#14で扱ってきたDATABASE_URLAUTH_GOOGLE_IDなどの環境変数は、本番用とプレビュー用で値を分けるのが望ましいです。

特にデータベースは、プレビュー環境が誤って本番のデータを書き換えてしまう事故を防ぐため、必ず別インスタンスを用意します。

Vercel

Vercelのダッシュボードでは、環境変数ごとに適用範囲を指定できます。

  1. SettingsEnvironment Variablesを開く
  2. 変数を追加する際、Production / Preview / Developmentのチェックボックスで適用範囲を選択
YAML
DATABASE_URL
  Production: postgresql://...本番用Supabaseプロジェクト
  Preview:    postgresql://...ステージング用Supabaseプロジェクト
  Development: postgresql://...ローカル用(.envファイルで管理)

Supabase側でも、本番用とステージング用でプロジェクトを分けて作成しておきます。

無料プランの範囲内でも複数プロジェクトを作成できるため、コストをかけずに環境を分離できます。

Google OAuthの「承認済みのリダイレクトURI」(#10参照)にも、それぞれの環境のURLを追加しておく必要があります。

YAML
http://localhost:3000/api/auth/callback/google
https://my-blog.vercel.app/api/auth/callback/google
https://my-blog-git-*.vercel.app/api/auth/callback/google  # プレビュー環境用(ワイルドカード非対応の場合は個別追加)

Googleの設定はワイルドカードに対応していない場合があります。

その場合は、ステージング専用の固定ブランチ(例:staging)を1つ用意し、そのブランチのプレビューURLだけを登録する運用にすると管理しやすくなります。

GitHub Actionsとは何か

ここから使うGitHub Actionsについて、先に説明しておきます。

GitHub Actionsは、GitHubに標準で組み込まれている自動化の仕組みです。

「プルリクエストが作られたら」「mainにpushされたら」といったGitHub上の出来事(イベント)をきっかけに、あらかじめ決めておいた処理(テストの実行やビルドなど)を自動で走らせることができます。

この記事で作るのは、「プルリクエストが作られたら、自動でテストを実行する」という仕組みです。

テストが通らなければ、そのプルリクエストはmainにマージできないようにする、というところまで含めて構築します。

全体の構成要素

GitHub Actionsは、いくつかの言葉を組み合わせて理解すると分かりやすくなります。

用語意味
ワークフロー(workflow)「何をきっかけに、何を実行するか」をまとめたファイル1つ全体のこと。.github/workflows/フォルダの中にYAMLファイルとして置く
トリガーonワークフローを起動するきっかけ。「プルリクエストが作られたとき」「mainにpushされたとき」など
ジョブjobsワークフローの中の作業のまとまり。「テストを実行するジョブ」のように、目的ごとに分ける
ステップstepsジョブの中の、1つ1つの具体的な処理。「依存関係をインストールする」「テストを実行する」など、上から順番に実行される
実行環境runs-onそのジョブをどんなマシン(OS)の上で動かすか。ubuntu-latestと書けば、GitHubが用意した使い捨てのLinux環境が使われる

これから作るワークフローファイルは、「プルリクエストが作られたとき(トリガー)」に、「テストという名前のジョブ(jobs)」が「Ubuntu環境(runs-on)」の上で、「依存関係のインストール→マイグレーション→テスト→ビルド、という一連のステップ(steps)」を順番に実行する、という中身になっています。

GitHub Actionsでテストを自動実行する

プレビュー環境は「デプロイはされるが、テストが通っているとは限らない」状態です。

ここに、#15で書いたテストを組み込み、テストが失敗しているコードはマージできないようにします。

ワークフローファイルを作成する

プロジェクトのルートに.github/workflows/というフォルダを作り、その中にci.ymlというファイルを作成します。

Bash
mkdir -p .github/workflows
YAML
# .github/workflows/ci.yml
name: CI

on:
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest

    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_USER: test
          POSTGRES_PASSWORD: test
          POSTGRES_DB: test_db
        ports:
          - 5432:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: "npm"

      - name: 依存関係をインストール
        run: npm ci

      - name: Prismaマイグレーションを実行
        run: npx prisma migrate deploy
        env:
          DATABASE_URL: postgresql://test:test@localhost:5432/test_db

      - name: ユニットテストを実行
        run: npm run test
        env:
          DATABASE_URL: postgresql://test:test@localhost:5432/test_db

      - name: ビルド
        run: npm run build
        env:
          DATABASE_URL: postgresql://test:test@localhost:5432/test_db
          AUTH_SECRET: test-secret
          AUTH_GOOGLE_ID: dummy
          AUTH_GOOGLE_SECRET: dummy

      - name: Playwrightブラウザをインストール
        run: npx playwright install --with-deps chromium

      - name: E2Eテストを実行
        run: npx playwright test
        env:
          DATABASE_URL: postgresql://test:test@localhost:5432/test_db
          PLAYWRIGHT_TEST: "true"

先ほどの用語と照らし合わせると、on.pull_requestトリガーjobs.testジョブ、その下の- uses: ...- name: ...が並んでいる部分がステップにあたります。

いくつか補足しておきます。

  • servicesで、本物のPostgreSQLコンテナをCI上に立てています。#15でPrismaをモック化したユニットテストとは別に、Playwrightで実行するE2Eテストは実際のDBに対して動作確認を行うため、CI環境にも使い捨てのDBが必要です
  • BETTER_AUTH_SECRET / GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRETはビルドを通すためのダミー値です。CIのテストでは実際のGoogleログインは行わず(#15のテスト用メール/パスワード認証を使う)、これらの値が本物である必要はありません
  • env:は、そのステップだけで使える環境変数を指定しています。CI環境には.envファイルが存在しないため、ここで明示的に渡す必要があります

重要

このci.ymlは、前のセクションで設定した「Production/Preview/Developmentごとの環境変数」とは無関係に動きます

GitHub ActionsのCIは、実際にアプリをデプロイしているわけではなく、「コードが正しく動くか」だけを使い捨ての環境で検証するものなので、常に同じダミー値で十分です。

Production/Previewの環境変数を実際に使い分けているのは、Vercel側のデプロイだけです。

プルリクエストを作ると、VercelのデプロイとGitHub ActionsのCIはそれぞれ独立して並行に動き、お互いの設定を参照し合うことはありません。

ワークフローファイルを配置する

作成したci.ymlは、他のコードと同じようにGitへコミットし、pushします。

Bash
git add .github/workflows/ci.yml
git commit -m "Add CI workflow"
git push

このファイルがmainブランチに存在していれば、今後プルリクエストを作成するたびに自動でこのワークフローが実行されます。

試しに適当なブランチを作ってプルリクエストを開き、GitHubの「Actions」タブ(リポジトリ上部のタブの1つ)を確認してみてください。

ワークフローが実行され、進行状況がリアルタイムで表示されるはずです。

mainブランチをブランチ保護する

GitHubリポジトリのSettingsBranchesで、mainブランチに対して以下を設定します。

  • 「Require status checks to pass before merging」を有効化し、先ほどのCIワークフロー(testジョブ)を必須項目として指定
  • 「Require a pull request before merging」を有効化

これにより、CIのテストが通らない限り、mainブランチにマージできなくなります

誤ってバグを含んだコードが本番にデプロイされる事故を、仕組みとして防げます。

リリースの流れ全体を確認する

ここまでの設定で、以下のような開発フローが完成します。

  1. feature/xxxブランチで機能を開発する
  2. プルリクエストを作成する
    • Vercelが自動でプレビュー環境をデプロイ → 目視で確認できる
    • GitHub Actionsがテストを自動実行 → ロジックレベルで壊れていないか確認できる
  3. レビューが完了し、CIが緑(成功)になったらmainにマージする
  4. mainへのマージをトリガーに、Vercelが本番環境へ自動デプロイする

「人間の目でのレビュー」と「機械によるテスト」の両方を通過したコードだけが本番に届く、という二重のチェック体制になっています。

デプロイ後に問題が起きた場合

万が一、本番デプロイ後に問題が発覚した場合は、Vercelのダッシュボードから即座に1つ前のデプロイにロールバックできます。

  1. Deploymentsタブを開く
  2. 問題が起きる前の、正常だったデプロイを選択
  3. 「Promote to Production」を選択

新しく修正コードをpushしてビルドし直すよりも、ロールバックの方が圧倒的に早く復旧できます。

まず切り戻して被害を止め、原因調査はその後で落ち着いて行う、という判断ができるようにしておくと安心です。

まとめ

この記事では、以下を実装しました。

  • Vercelのプレビュー環境を使い、プルリクエストごとに確認用の環境を自動生成
  • 本番・プレビュー・開発で環境変数(特にDATABASE_URL)を分離
  • GitHub Actionsで、ユニットテスト・E2Eテスト・ビルドを自動実行するCIパイプラインを構築
  • ブランチ保護により、テストが通らないコードは本番にマージできない仕組みを整備

これで、安心して機能追加を続けられる開発基盤が整いました。

いよいよ次回、最終回の#18 ミニブログを完成させる — シリーズ総仕上げでは、実践編で学んだすべての要素を振り返りながら、詰まりやすいポイントのFAQと、この先の学習ロードマップをまとめます。