1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GCPでNext.jsアプリを動かす!最適なデプロイ戦略でやってよかった3つのこと

1
Posted at

多くのNext.js開発者が直面する共通の悩み、それは「本番環境への最適なデプロイ先はどこか?」という問題ではないでしょうか。特にGCPを検討する際、Cloud Run, Cloud Functions, App Engineといった多様な選択肢の中から、どれを選べば良いのか迷うことは少なくありません。

この記事では、Next.jsアプリケーションをGCPにデプロイする際の最適な戦略を、コスト、スケーラビリティ、開発体験の観点から徹底比較します。そして、実際に私が試行錯誤の末にたどり着いた「GCPでNext.jsアプリを動かす」ためのベストプラクティスと、やってよかった3つのことを具体的なコード例を交えて解説します。この記事を読めば、あなたのNext.jsアプリをGCPで自信を持ってデプロイできるようになるでしょう。

GCPでNext.jsアプリを動かす!最適なデプロイ戦略でやってよかった3つのこと

Next.jsアプリケーションをGCPにデプロイする際、選択肢が多すぎて迷うのは自然なことです。ここでは、主要なデプロイ先候補であるCloud Run、App Engine、Cloud Functionsの特性を比較し、なぜCloud Runが多くのNext.jsアプリケーションにとって最適な選択肢となるのかを解説します。

GCPにおけるNext.jsデプロイ先の比較とCloud Runの優位性

このセクションでは、GCPの主要なデプロイサービスをNext.jsアプリケーションの観点から比較し、それぞれのメリット・デメリットを明確にします。

Cloud Run

  • メリット:
    • コンテナベースのフルマネージドサーバーレス: Next.jsはNode.jsアプリケーションであり、Dockerコンテナとしてパッケージ化することで、SSR (Server-Side Rendering) や ISR (Incremental Static Regeneration) を含むフル機能をサポートします。Cloud Runはコンテナをそのまま実行できるため、Next.jsの機能を最大限に活用できます。
    • 高いスケーラビリティとコスト効率: リクエストに応じてゼロからスケールアウトし、アイドル時にはゼロにスケールインするため、トラフィックの変動が大きいアプリケーションでコストを最適化できます。
    • 任意の言語・フレームワークに対応: Node.jsだけでなく、GoやPythonなど任意のランタイムで動作するため、将来的な技術スタックの変更にも柔軟に対応できます。
  • デメリット:
    • コンテナイメージの管理: Dockerfileの作成やイメージのビルド・プッシュといったコンテナに関する知識が必要になります。
    • Vercelのような開発者体験との比較: Next.jsの公式ホスティングであるVercelと比較すると、ビルドやデプロイのシームレスさは一歩譲りますが、GCPの豊富なサービスとの連携やコスト最適化の点で優位性があります。

App Engine (Standard/Flexible)

  • メリット:
    • フルマネージドでインフラ管理が不要: 開発者はコードに集中でき、インフラのプロビジョニングやスケーリングをGCPが自動で行います。
    • 様々な言語ランタイムをサポート: Node.jsアプリケーションもデプロイ可能です。
  • デメリット:
    • Next.jsの特性を活かしにくいケースも: Standard環境では特定のランタイムバージョンにロックされることがあり、Next.jsの最新機能や複雑なビルドプロセスに対応しにくい場合があります。Flexible環境であればコンテナを実行できますが、Cloud Runの方がよりサーバーレスで柔軟な料金体系です。
    • コスト: Cloud Runと比較して、アイドル時のコストが発生する可能性があり、トラフィックの少ないアプリケーションでは割高になることがあります。

Cloud Functions

  • メリット:
    • イベント駆動のサーバーレス: APIルートや特定のSSRリクエストを処理する小さな関数を実行するのに適しています。
    • Firebase Hostingとの組み合わせ: 静的コンテンツをFirebase Hostingで配信し、動的な部分をCloud Functionsで処理する構成は、小規模なNext.jsサイトで有効な場合があります。
  • デメリット:
    • Next.jsのフル機能サポートの制限: next/imageの画像最適化など、一部のNext.js機能がCloud Functions環境で完全にサポートされない場合があります(Next.js Docs: Image Optimization)。Next.js 16.2以降のDeployment Adapter APIにより改善傾向にありますが、それでもCloud Runの方がより自然です。
    • コールドスタート: リクエストがない状態から関数が起動するまでに時間がかかり、SSRのパフォーマンスに影響を与える可能性があります。
    • 複雑なルーティング: App RouterのようなNext.jsの高度なルーティングをCloud Functionsで完全に再現するのは、追加の工夫が必要になる場合があります。

結論として、ほとんどのNext.jsアプリケーションにおいて、GCP上での第一の選択肢はCloud Runです。 特にSSRやISRをフル活用し、スケーラビリティとコスト効率を両立させたい場合に最適です。

Next.jsアプリケーションの準備とGCPへのデプロイ手順

このセクションでは、Next.jsアプリケーションをGCPにデプロイするために必要な準備と具体的な手順を解説します。

1. Next.jsアプリケーションの準備

Next.jsアプリケーションをDockerコンテナとして効率的にデプロイするには、next.config.jsでoutput: 'standalone'を設定することが非常に重要です(Next.js Docs: Output File Tracing)。これにより、本番環境に必要なファイルのみがビルド成果物に含まれ、Dockerイメージサイズを大幅に削減できます。

// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'standalone', // Standalone出力モードを有効化
  images: {
    remotePatterns: [ // 外部画像ホストを許可
      { protocol: 'https', hostname: 'storage.googleapis.com' },
      { protocol: 'https', hostname: '*.googleusercontent.com' },
      // 必要に応じて他のドメインを追加
    ],
  },
  // その他のNext.js設定
};

module.exports = nextConfig;

次に、output: 'standalone'を最大限に活かすためのMulti-stage Dockerfileを作成します。これは、依存関係のインストール、Next.jsのビルド、そして最終的なランナーイメージの作成を分離することで、軽量でセキュアなコンテナイメージを構築するベストプラクティスです。

# Stage 1: Dependenciesのインストール
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
# npm ci は package-lock.json に基づいて依存関係を厳密にインストール
RUN npm ci --legacy-peer-deps

# Stage 2: Next.jsアプリケーションのビルド
FROM node:20-alpine AS builder
WORKDIR /app
# 依存関係を前のステージからコピー
COPY --from=deps /app/node_modules ./node_modules
# アプリケーションコードをコピー
COPY . .
# 本番環境向けに設定
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1 # Next.jsテレメトリを無効化(任意)
# Next.jsアプリケーションをビルド
RUN npm run build

# Stage 3: 本番環境用ランナーイメージの作成
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV PORT=3000 # Cloud RunはデフォルトでPORT環境変数を使用

# 非rootユーザーの作成と設定
# セキュリティベストプラクティスとして非rootユーザーで実行
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

# ビルドされたファイルをコピー
# standalone出力モードにより、必要なファイルのみがコピーされる
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static

# ファイルの所有権を非rootユーザーに変更
RUN chown -R nextjs:nodejs /app
USER nextjs # 非rootユーザーで実行

EXPOSE 3000 # コンテナがリッスンするポート
CMD ["node", "server.js"] # Next.jsサーバーを起動

このDockerfileでは、output: 'standalone'で生成された.next/standaloneディレクトリを最終イメージにコピーすることで、不要なnode_modulesを含まずに、Next.jsアプリケーションを実行するために必要な最小限のファイルのみをパッケージ化しています。また、セキュリティ向上のため、非rootユーザーで実行するように設定しています。

2. GCP Cloud Runへのデプロイ

Cloud Runへのデプロイは、大きく分けて2つの方法があります。

A. ソースコードから直接デプロイ

最もシンプルな方法で、gcloud run deployコマンドが自動的にDockerfileを検出し、Cloud Buildを使用してイメージをビルドし、Artifact RegistryにプッシュしてからCloud Runにデプロイします(Google Cloud Docs: Cloud Run)。

# プロジェクトIDとリージョンを設定
export PROJECT_ID="your-gcp-project-id"
export REGION="asia-northeast1" # 例: 東京リージョン
export SERVICE_NAME="your-nextjs-app-service"

gcloud run deploy "${SERVICE_NAME}" \
  --source . \
  --project="${PROJECT_ID}" \
  --region="${REGION}" \
  --allow-unauthenticated # 認証なしでアクセスを許可する場合
  # --set-env-vars KEY=VALUE # 環境変数を設定する場合
  # --cpu=1 --memory=512Mi --min-instances=0 --max-instances=1 # リソース設定の例

B. DockerイメージをArtifact Registryにプッシュしてからデプロイ

CI/CDパイプラインをより細かく制御したい場合や、ローカルでイメージをビルドしてテストしたい場合に適しています。

# プロジェクトIDとリージョンを設定
export PROJECT_ID="your-gcp-project-id"
export REGION="asia-northeast1"
export REPOSITORY_NAME="nextjs-repo" # Artifact Registryのリポジトリ名
export IMAGE_NAME="your-nextjs-app"
export SERVICE_NAME="your-nextjs-app-service"

# 1. Artifact Registryリポジトリの作成 (初回のみ)
# Dockerイメージを保存するリポジトリを作成します。
gcloud artifacts repositories create "${REPOSITORY_NAME}" \
  --repository-format=docker \
  --location="${REGION}" \
  --description="Docker repository for Next.js applications" \
  --project="${PROJECT_ID}"

# 2. Dockerイメージのビルドとタグ付け
# -platform linux/amd64 はM1/M2 Macなどでビルドする際に必要
docker build -t "${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPOSITORY_NAME}/${IMAGE_NAME}:latest" . --platform linux/amd64

# 3. Dockerイメージのプッシュ
# ビルドしたイメージをArtifact Registryにアップロードします。
docker push "${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPOSITORY_NAME}/${IMAGE_NAME}:latest"

# 4. Cloud Runへのデプロイ
# Artifact Registryに保存されたイメージを指定してCloud Runにデプロイします。
gcloud run deploy "${SERVICE_NAME}" \
  --image "${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPOSITORY_NAME}/${IMAGE_NAME}:latest" \
  --platform managed \
  --region "${REGION}" \
  --allow-unauthenticated \
  --project="${PROJECT_ID}"
  # --set-env-vars KEY=VALUE # 環境変数を設定する場合

3. データベース(Cloud SQL for PostgreSQL)との接続

Next.jsアプリケーションからCloud SQLのようなマネージドデータベースに接続する場合、Cloud SQL Auth Proxyを利用するのが一般的ですが、Node.jsアプリケーションでは@google-cloud/cloud-sql-connectorライブラリを使うとよりシンプルに接続を確立できます。

まず、必要なライブラリをインストールします。

npm install pg @google-cloud/cloud-sql-connector
npm install --save-dev @types/pg # TypeScriptを使用する場合

次に、接続コードの例です。サーバーコンポーネントやAPIルート内で使用することを想定しています。

// lib/db.ts (例)
import { Connector } from '@google-cloud/cloud-sql-connector';
import pg from 'pg';

const connector = new Connector();

// Cloud SQLインスタンス接続名 (例: project-id:region:instance-name)
const instanceConnectionName = process.env.CLOUD_SQL_CONNECTION_NAME;
// データベースユーザー、パスワード、データベース名
const dbUser = process.env.DB_USER;
const dbPass = process.env.DB_PASS;
const dbName = process.env.DB_NAME;

async function getPgClient() {
  if (!instanceConnectionName || !dbUser || !dbPass || !dbName) {
    throw new Error('Cloud SQL connection environment variables are not set.');
  }

  // Cloud SQL Connector経由でソケット接続オプションを取得
  const clientOpts = await connector.get
  const client = new pg.Client({
    user: dbUser,
    password: dbPass,
    database: dbName,
    host: '127.0.0.1', // Cloud SQL Connectorはローカルプロキシとして動作
    port: clientOpts.port,
  });

  await client.connect();
  return client;
}

export default getPgClient;

このコードでは、@google-cloud/cloud-sql-connectorがCloud SQLインスタンスへの安全な接続を確立し、アプリケーションからは127.0.0.1のローカルポート経由でアクセスできるようにします。Cloud Runサービスには、Cloud SQLインスタンスへの接続権限(Cloud SQL クライアントロールなど)を付与することを忘れないでください。環境変数はSecret Managerで管理し、Cloud Runにマウントするのがベストプラクティスです(Google Cloud Docs: Secret Manager)。

GCPでNext.jsアプリをデプロイして「やってよかった」3つのこと

実際にGCPでNext.jsアプリケーションを運用してみて、特に効果を実感した3つの実践的な戦略を紹介します。

1. Standalone出力モードとMulti-stage Dockerfileによるイメージサイズ最適化

next.config.jsでoutput: 'standalone'を設定し、上記で示したMulti-stage Dockerfileを組み合わせたことで、最終的なDockerイメージサイズを大幅に削減できました。

  • メリット:
    • デプロイ時間の短縮: イメージが小さくなることで、Artifact RegistryへのプッシュやCloud Runへのデプロイが高速化されます。
    • コールドスタート時間の改善: コンテナの起動に必要なリソースが減るため、Cloud Runのコールドスタート時間が短縮され、ユーザー体験が向上します。
    • ストレージコストの削減: Artifact Registryに保存されるイメージの容量が減り、関連するコストを抑えられます。
    • セキュリティの向上: 不要なファイルや開発依存関係が含まれないため、攻撃対象領域が減少します。

2. React Server Components (RSC) の積極的な活用とApp Routerの導入

Next.jsのApp RouterとReact Server Components (RSC) を積極的に活用することで、アプリケーションのパフォーマンスと開発効率を大きく向上させることができました。

  • メリット:
    • クライアントバンドルサイズの削減: データフェッチングや重い処理をサーバーコンポーネントに集約することで、ブラウザに送信されるJavaScriptの量を減らし、Core Web Vitalsを改善しました(Next.js Docs: React Server Components)。
    • 高速なデータ取得: サーバーコンポーネントはデータベースやAPIから直接データをフェッチできるため、クライアントとサーバー間のネットワーク往復が減り、レンダリングが高速化されます。
    • 開発体験の向上: App Routerのネストされたレイアウトやストリーミング機能により、複雑なUIの構築がより直感的になりました(Next.js Docs: App Router)。

RSCをどこまで使うか、クライアントコンポーネントとの境界線をどこに引くかについては、アプリケーションの特性やインタラクティブ性の要件によって異なりますが、基本的には「データフェッチと静的なUIはサーバーコンポーネント、インタラクティブな部分は'use client'でクライアントコンポーネント」という原則で進めるのが良いでしょう。

3. Cloud BuildとSecret Managerを活用したCI/CDパイプラインの構築

手動デプロイによるヒューマンエラーをなくし、開発のサイクルを高速化するために、Cloud BuildとSecret Managerを組み合わせたCI/CDパイプラインを構築しました。

  • メリット:
    • デプロイの自動化と信頼性向上: GitHubへのプッシュをトリガーに、自動的にテスト、ビルド、デプロイが実行されるため、手動作業によるミスがなくなり、一貫したデプロイプロセスが保証されます(Google Cloud Docs: Cloud Build)。
    • セキュリティの強化: APIキーやデータベース認証情報などの機密性の高い環境変数はSecret Managerで一元管理し、Cloud BuildやCloud Runから安全にアクセスできるようにしました(Google Cloud Docs: Secret Manager)。環境変数をコードリポジトリに含めるリスクを排除できます。
    • 開発者の負担軽減: デプロイ作業から解放され、開発者は機能開発に集中できるようになりました。

Cloud Buildの設定ファイル (cloudbuild.yaml) の例です。

# cloudbuild.yaml
steps:
  # 1. Dockerイメージのビルド
  - name: 'gcr.io/cloud-builders/docker'
    args: ['build', '-t', '${_LOCATION}-docker.pkg.dev/${PROJECT_ID}/${_REPOSITORY_NAME}/${_IMAGE_NAME}:${COMMIT_SHA}', '.']
    env:
      - 'NODE_ENV=production' # ビルド時の環境変数を設定
      # NEXT_PUBLIC_で始まる環境変数はここに設定
      # - 'NEXT_PUBLIC_API_URL=${_NEXT_PUBLIC_API_URL}' 
  
  # 2. DockerイメージをArtifact Registryにプッシュ
  - name: 'gcr.io/cloud-builders/docker'
    args: ['push', '${_LOCATION}-docker.pkg.dev/${PROJECT_ID}/${_REPOSITORY_NAME}/${_IMAGE_NAME}:${COMMIT_SHA}']

  # 3. Cloud Runにデプロイ
  - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
    entrypoint: gcloud
    args:
      - 'run'
      - 'deploy'
      - '${_SERVICE_NAME}'
      - '--image'
      - '${_LOCATION}-docker.pkg.dev/${PROJECT_ID}/${_REPOSITORY_NAME}/${_IMAGE_NAME}:${COMMIT_SHA}'
      - '--region'
      - '${_LOCATION}'
      - '--platform'
      - 'managed'
      - '--allow-unauthenticated'
      # Secret Managerから環境変数をマウント
      - '--update-secrets'
      - 'DB_USER=DB_USER:latest'
      - 'DB_PASS=DB_PASS:latest'
      - 'DB_NAME=DB_NAME:latest'
      - 'CLOUD_SQL_CONNECTION_NAME=CLOUD_SQL_CONNECTION_NAME:latest'
    # Cloud RunサービスアカウントにCloud SQLクライアントロールを付与する必要があります。
    # --service-account を使用して特定のサービスアカウントを指定することも可能
images:
  - '${_LOCATION}-docker.pkg.dev/${PROJECT_ID}/${_REPOSITORY_NAME}/${_IMAGE_NAME}:${COMMIT_SHA}'

substitutions:
  _LOCATION: asia-northeast1 # デプロイ先のリージョン
  _REPOSITORY_NAME: nextjs-repo # Artifact Registryのリポジトリ名
  _IMAGE_NAME: your-nextjs-app # Dockerイメージ名
  _SERVICE_NAME: your-nextjs-app-service # Cloud Runサービス名
  # NEXT_PUBLIC_で始まる環境変数をここで定義し、ビルドステップで利用
  # _NEXT_PUBLIC_API_URL: https://api.example.com

このcloudbuild.yamlでは、ビルド時にNODE_ENV=productionを設定し、Cloud Runへのデプロイ時にSecret Managerから機密情報を環境変数としてマウントしています。

よくあるエラーと回避策:GCPでのNext.jsデプロイの落とし穴

Next.jsをGCPにデプロイする際によく遭遇する問題とその解決策を知っておくことで、スムーズな開発・運用が可能になります。

Hydration Error (Mismatch between server and client)

  • 説明: クライアントサイドでレンダリングされたコンテンツが、サーバーサイドでレンダリングされたコンテンツと一致しない場合に発生します。これはNext.jsのSSRの仕組み上、よくある問題です。
  • 原因: サーバーとクライアント間で動的に変化するコンテンツ(例: Dateオブジェクトを直接レンダリング、localStorageへのアクセス)や、不適切なHTMLタグの使用などが挙げられます。
  • 回避策:
    • サーバーとクライアント間でレンダリングされるコンテンツの一貫性を確保します。
    • 動的に変化するコンテンツは、useEffectフック内でクライアントサイドでのみレンダリングするか、suppressHydrationWarningプロパティを一時的に使用して警告を抑制します(ただし、根本原因の解決が望ましい)。
    • typeof window !== 'undefined'のような条件分岐で、クライアントサイドでのみ実行されるコードを保護します。

documentまたはwindowオブジェクトエラー (SSR時のブラウザAPIアクセス)

  • 説明: Next.jsがサーバーサイドでレンダリングを実行している際に、ブラウザ固有のグローバルオブジェクト(window, document, localStorageなど)に直接アクセスしようとした際に発生します。
  • 原因: コンポーネントがマウントされる前に、ブラウザ固有のAPIにアクセスするコードが実行されるためです。
  • 回避策:
    • useEffectフックを使用して、ブラウザのwindowオブジェクトを必要とするコードブロックを、ページコンポーネントがマウントされた後にのみ実行されるようにします。
    • クライアントサイドでのみ実行されるコンポーネントは、next/dynamicで動的インポートし、ssr: falseオプションを設定します。

Cloud Runでの環境変数設定ミス

  • 説明: アプリケーションが期待する環境変数がCloud Runコンテナ内で利用できず、エラーが発生します。
  • 原因: gcloud run deployコマンドで--set-env-varsオプションを忘れた、またはSecret Managerと連携する設定が不十分。NEXT_PUBLIC_プレフィックスのない環境変数はビルド時ではなく実行時に設定される必要があります。
  • 回避策:
    • 機密性の高い環境変数はSecret Managerに保存し、Cloud RunサービスにSecret Managerへのアクセス権限を付与した上で、--update-secretsオプションでSecret Managerのシークレットを環境変数としてマウントします。
    • NEXT_PUBLIC_プレフィックスを持つ環境変数はビルド時に埋め込まれるため、Dockerfileのビルドステージで設定するか、Cloud BuildなどのCI/CDパイプラインでビルド時に渡す必要があります。

まとめ:GCPでNext.jsデプロイを成功させるために

この記事では、GCPでNext.jsアプリケーションをデプロイする際の最適な戦略としてCloud Runを推奨し、その具体的な手順と「やってよかった」3つのことを解説しました。

  • Cloud RunがNext.jsデプロイの第一選択肢: フルマネージドなコンテナ実行環境として、Next.jsのSSR/ISRを最大限に活用でき、スケーラビリティとコスト効率に優れています。
  • Standalone出力モードとMulti-stage Dockerfile: Dockerイメージサイズの最適化は、デプロイ時間の短縮、コールドスタートの改善、コスト削減に直結します。
  • React Server Components (RSC) とApp Routerの活用: パフォーマンス向上と開発体験の改善に大きく貢献します。
  • CI/CDパイプラインとSecret Manager: 自動化とセキュリティ強化は、安定した運用と開発効率の向上に不可欠です。

GCPはVercelとは異なる開発者体験を提供しますが、大規模なエンタープライズアプリケーションや既存のGCPインフラとの統合が必要な場合、またはより詳細なインフラ制御やコスト最適化を求める場合には、非常に強力な選択肢となります。

今回紹介したデプロイ戦略が、あなたのNext.jsプロジェクトをGCPで成功させる一助となれば幸いです。さらに詳細な設定やトラブルシューティングについては、各サービスの公式ドキュメントを参照することをお勧めします。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?