MENU

問い合わせ


    【Next.jsで作るオンライン予約サービス 第11回】Vercelに公開する(環境変数・DB接続・本番チェックで「公開したら動かない」を防ぐ)

    手元で動いていたサービスが、公開した途端に動かない。予約サービスでは、決済のあとに戻ってくるURLがずれる、確認メールのリンクが違うサイトを指す、といった形で現れます。第10回までで予約・決済・メール・管理画面・自動テストがそろったので、最終回の今回は、Vercel(Next.jsの開発元が運営する公開サービス)に公開するための準備と、本番チェックをまとめます。

    「開発会社から『本番に公開しました』と報告を受けたけれど、本当に本番で動く状態なのか、発注側では何を見て確認すればいいの?」

    先に結論です。公開作業で一番事故が起きやすいのは、コードではなく「設定(環境変数)」と「定期実行」です。 本番で必要な設定を一覧にし、公開前に機械でチェックし、公開後に実際に1件予約して確かめる。この3つを手順にしておけば、「公開したら動かない」はほとんど防げます。

    前回の第10回では、予約の通しテストを自動化しました。詳しくは次の記事で解説しています。

    目次

    この回で作るもの:本番用の設定と公開前チェック

    使ったバージョンは Next.js 16・PostgreSQL 16 で、コードは blogs/it_hacchu/series/next-yoyaku/code/ にあります。

    作るもの役割主なファイル
    サイトURLの一本化メールのリンクや決済後の戻り先を、1か所の関数で決めるlib/site-url.ts
    本番の環境変数チェック未設定・テスト用の値の入れ忘れを、公開前に見つけるlib/env-check.ts、scripts/check-env.ts
    定期実行の設定期限切れの仮予約の整理と、確認メールの再送を一定間隔で呼ぶvercel.json
    DB接続数の調整Vercel上で接続数が増えすぎないようにするlib/db.ts

    公開前に決めておくこと:環境変数の一覧

    環境変数とは、パスワードやAPIキーのように「コードに書かず、動かす場所ごとに別の値を渡す設定」のことです。本番とテストで値が違うものは、すべてここに入ります。Vercelでは、プロジェクトの設定画面から環境(Production / Preview / Development)ごとに登録できます。

    名前本番での値入れ忘れたときの症状
    DATABASE_URL本番DBの接続先画面が開かない
    APP_URL本番のURL(https:// で始まる)メールのリンクや決済後の戻り先が違うサイトになる
    STRIPE_SECRET_KEY本番用の鍵(sk_live_)テスト用の鍵のままだと、実際の決済ができない
    STRIPE_WEBHOOK_SECRET本番用Webhookの署名用シークレット(whsec_)支払いが済んでも予約が確定しない
    CRON_SECRET16文字以上のランダムな文字列定期実行が断られる
    RESEND_API_KEY、MAIL_FROMメール送信サービスの鍵と、認証済みの送信元確認メール・ログインメールが届かない
    ADMIN_EMAILS管理者のメールアドレス管理画面に誰も入れない

    Stripeのテスト用と本番用は、鍵もWebhookの登録も別です。テストで通ったからといって、本番用の設定が済んだことにはなりません。

    実装1:サイトのURLを1か所で決める

    第5〜8回では、メールのリンクや決済後の戻り先のURLを、場所ごとに作っていました。ログインメールはリクエストのホスト名から作る書き方でした。この方式だと、偽のホスト名を付けて呼び出されたとき、他人のサイトへ誘導するリンクがメールに入るおそれがあります。そこで、URLを作る関数を1つにまとめ、本番では環境変数で固定しました。

    // lib/site-url.ts
    export function siteUrl(env: Record<string, string | undefined> = process.env): string {
      const fixed = env.APP_URL?.trim();
      if (fixed) return fixed.replace(/\/+$/, "");
      // Vercel が自動で入れる本番ドメイン(スキームなし)。APP_URL を入れ忘れたときの保険
      const vercelHost = env.VERCEL_PROJECT_PRODUCTION_URL?.trim();
      if (vercelHost) return `https://${vercelHost}`;
      if (env.NODE_ENV === "production") {
        throw new Error("APP_URL が設定されていません(本番では必須。.env.example を参照)");
      }
      return "http://localhost:3000";
    }

    VERCEL_PROJECT_PRODUCTION_URL は、Vercelがプロジェクトの本番ドメイン(スキームなし)を自動で入れてくれる環境変数です。確認用のプレビュー環境でも本番のドメインが入るため、あくまで入れ忘れたときの保険にとどめ、APP_URL を主にしています。どちらも無い本番では、間違ったリンクを作るより止まるほうがよいので、エラーにしています。

    📰 出典:Vercel Docs「System environment variables」

    ログインのServer Action(app/login/actions.ts)、予約のServer Action(app/spaces/[slug]/reserve/actions.ts)、Stripeの通知を受ける入口(app/api/stripe/webhook/route.ts)、定期実行の入口(app/api/cron/expire-holds/route.ts)の4か所を、この関数に置き換えました。

    実装2:本番の環境変数を公開前にチェックする

    環境変数の入れ忘れは、人の目では見落とします。そこで「そろっているか」と「本番向けの値か」を調べる関数を作り、コマンドで実行できるようにしました。値そのものは出力せず、名前と状態だけを返します(ログに秘密が残らないようにするため)。

    // lib/env-check.ts(抜粋)
    export function checkProductionEnv(env: Env = process.env): EnvIssue[] {
      const issues: EnvIssue[] = [];
      for (const name of REQUIRED) {
        if (!env[name]?.trim()) issues.push({ name, problem: "missing" });
      }
      const has = (name: string) => !issues.some((i) => i.name === name);
      // 本番の URL は https。http のままだと Cookie の Secure 属性やメールのリンクが崩れる
      if (has("APP_URL") && !/^https:\/\//.test(env.APP_URL!.trim())) issues.push({ name: "APP_URL", problem: "invalid" });
      // 本番にテスト用の鍵(sk_test_)を入れたまま公開すると、実際には誰からもお金を受け取れない
      if (has("STRIPE_SECRET_KEY") && !/^sk_live_/.test(env.STRIPE_SECRET_KEY!.trim())) issues.push({ name: "STRIPE_SECRET_KEY", problem: "invalid" });
      // (STRIPE_WEBHOOK_SECRET の形式、CRON_SECRET の長さも同様に確認)
      return issues;
    }
    # 本番の値を一時ファイルに取り出して確認する(このファイルは git に入れない)
    node --env-file=.env.production scripts/check-env.ts

    何も設定していない状態で実行すると、8項目すべてが「未設定」と表示され、終了コード1で止まります。単体テスト(tests/env-check.test.ts、tests/site-url.test.ts)も追加し、npm test に含めています。

    実装3:定期実行(Cron)を設定する

    第7〜8回で、「期限切れの仮予約を片づける」「未送信の確認メールを送り直す」入口(/api/cron/expire-holds)を作りました。Vercelでは、vercel.json に書くだけで、この入口を一定間隔で呼んでくれます。

    {
      "$schema": "https://openapi.vercel.sh/vercel.json",
      "crons": [{ "path": "/api/cron/expire-holds", "schedule": "*/10 * * * *" }]
    }

    CRON_SECRET という名前の環境変数をVercelに登録しておくと、Vercelが呼び出すときに Authorization: Bearer <値> のヘッダーを自動で付けます。第7回の入口はすでにこの形で確認しているので、コードの変更は不要でした。Vercelの公式ドキュメントも、16文字以上のランダムな文字列を勧めています。

    📰 出典:Vercel Docs「Managing Cron Jobs」

    注意したいのは料金プランの制限です。 執筆時点(2026年10月)のドキュメントでは、無料のHobbyプランは1日1回までで、それより細かい間隔の設定は公開(デプロイ)の段階でエラーになります。10分ごとに回すには有料のProプラン以上が必要です。また、定期実行は取りこぼしや二重実行が起こりうるため、処理は「何度実行しても同じ結果になる」作りにしておく必要があります(本連載の仮予約の整理とメール再送は、その作りです)。

    📰 出典:Vercel Docs「Usage & Pricing for Cron Jobs」

    実装4:DB接続数をVercel向けに絞る

    Vercelの関数は、アクセスが増えると同時にいくつも起動します。1つの関数が複数のDB接続を持つと、合計がDBの接続上限をすぐ超えます。そこで、Vercel上では接続を1本に絞りました。

    // lib/db.ts(抜粋)
    export function getDb() {
      // Vercel の関数は同時にいくつも起動する。1つが5本ずつ接続を持つとDBの上限をすぐ超えるので、1本にする
      const max = process.env.VERCEL ? 1 : 5;
      // 接続プーラー(トランザクションモード)経由ではプリペアドステートメントが使えない
      globalForDb.db ??= drizzle(postgres(connectionUrl(), { max, prepare: !process.env.VERCEL }), { schema });
      return globalForDb.db;
    }

    マネージドDB(Neon、Supabaseなど)には、接続をまとめて受ける「接続プーラー」用のURLが別に用意されていることが多く、その場合は DATABASE_URL にそちらを入れます。どのURLを使うかは、DB事業者の案内を確認してください。マイグレーション(テーブルの作成・変更)は、プーラー経由ではなく直接つなぐURLで実行するのが一般的です。

    公開後の本番チェック

    公開できたら、次の順に確認します。1〜2は自動、3以降は人が1回ずつ確かめます。

    • ☐ /api/health が200で、データベースが up になっている
    • ☐ Vercelの設定画面の「Cron Jobs」に /api/cron/expire-holds が表示され、手動実行で401にならない
    • ☐ 会員ログインのメールが届き、リンクのURLが本番のドメインになっている
    • ☐ Stripeの本番用Webhookに、本番URLの /api/stripe/webhook を登録し、署名用シークレットを STRIPE_WEBHOOK_SECRET に入れた
    • ☐ 少額のスペースで1件予約し、支払い後に予約が「確定」になり、確認メールが届く
    • ☐ 同じ予約をキャンセルし、返金されることをStripeの管理画面で確かめる
    • ☐ 管理者以外のアカウントで /admin を開くと、404になる

    動作確認でできたこと・できていないこと

    開発環境で、次のことを確認しました。

    • npm run lint、npx tsc --noEmit が通った
    • 単体テスト(npx vitest run)が通った。ただしDBにつなぐテスト45件は、この環境にPostgreSQLを起動できなかったため実行していない
    • next build --webpack が成功した(この環境ではTurbopackのネイティブ版が動かないため、--webpack を付けて確認している)
    • scripts/check-env.ts が、未設定の環境で8項目すべてを指摘して終了コード1になる

    Vercelへの実際の公開、本番のStripe・メール送信・独自ドメインでの動作は未確認です。 この記事の手順は、公式ドキュメントとコードの確認にとどまります。実際の公開では、上のチェックリストで必ず確かめてください。

    つまずきやすい点・セキュリティ上の注意

    • 環境変数を変えたら、再デプロイが必要。 Vercelでは、変更後の値は新しく作った公開版から反映されます
    • NEXT_PUBLIC_ で始まる環境変数は、ブラウザに公開される。 秘密の鍵には絶対に付けない
    • プレビュー環境に本番の鍵を入れない。 確認用の環境から本番の決済やメールが動いてしまう
    • 本番DBへのマイグレーションは、公開の手順に組み込む。 順番を間違えると、新しいコードが存在しない列を読んで落ちる
    • 本連載で省略したもの:監視・障害通知、バックアップと復旧の訓練、アクセス集中への備え、独自ドメインとメール認証(SPF・DKIMなど)の設定。実運用では必要です

    発注者向けメモ:公開作業を頼むときの確認点

    公開(リリース)は、機能の開発と違って地味ですが、事故が起きやすい工程です。開発会社に頼むときは、次を確認しておくと安心です。

    • ☐ 本番で使う設定(環境変数)の一覧と、その管理者(誰がパスワードや鍵を持つか)を決めている
    • ☐ Vercel・DB・Stripe・メール送信サービスなどのアカウントが、発注者の名義で作られている(開発会社の個人名義にしない)
    • ☐ 公開前チェックのリストがあり、公開後に実際に1件予約して確かめた記録がある
    • ☐ 定期実行に必要な料金プランを、月額の費用を含めて確認した
    • ☐ 公開に失敗したときに前の版へ戻す方法(ロールバック)と、連絡先が決まっている
    • ☐ 本番運用の月額(Vercel・DB・メール・決済手数料)の見込みを、見積もりとは別に聞いている

    開発会社への質問例は、次のとおりです。

    • 「本番に必要な設定の一覧と、公開前に機械で確かめる仕組みはありますか」
    • 「テスト用の鍵と本番用の鍵は、誰がどこで管理しますか。担当が変わったときの引き継ぎはどうなりますか」
    • 「定期実行が止まったり、二重に動いたりしたとき、予約や返金にどんな影響がありますか」
    • 「公開後に問題が見つかったとき、どのくらいの時間で前の版へ戻せますか」

    まとめと次回予告

    最終回では、サイトのURLを1か所に決める、本番の環境変数を公開前に機械で確かめる、定期実行を設定する、DB接続数をVercel向けに絞る、という4点を足し、公開後のチェックリストにまとめました。

    第0回の雛形づくりから、一覧・カレンダー・予約フォーム・二重予約の防止・会員ログイン・決済・Webhook・メールと返金・管理画面・テストを経て、貸し会議室のオンライン予約サービスを作る連載はここで完結です。この連載で一貫していたのは、「お金」と「枠」を扱うシステムでは、機能の数よりも、壊れたときに被害が広がらない作りが大切ということでした。

    この連載の記事一覧

    この記事は連載「Next.jsで作るオンライン予約サービス」の1回です。連載のほかの回は次のとおりです(連載の一覧ページ)。

    システム制作・運用・保守のお問い合わせはこちら


      よかったらシェアしてね!
      • URLをコピーしました!
      • URLをコピーしました!

      この記事を書いた人

      株式会社THIRD HERO代表取締役 朝野貴朗
      Webシステム開発を中心に、toC向けサービスサイトの運営、ツール開発などを行ってまいりました。

      コメント

      目次