Next.js: 最終プロジェクト: デプロイと CI/CD
最終更新:2026-08-26
コードのコミットからユーザーのブラウザに届くまでには、デプロイ、CI/CD、監視、パフォーマンス最適化といういくつかのステップがあります — これがアプリケーションを真に「稼働」させるための最後のステップです。
1. 学習内容
- 完全な Vercel デプロイプロセス(vercel.json + PostgreSQL + カスタムドメイン + 環境変数)
- セルフホスト Docker ソリューション(マルチステージ Dockerfile ビルド + docker-compose.yml + Nginx + PM2)
- GitHub Actions CI/CD パイプライン(lint → test → build → deploy → Lighthouse CI)
- PostHog イベントトラッキング + @vercel/speed-insights + Sentry エラー監視
- 実践的なパフォーマンス最適化(Lighthouse 90+/100+ next/image 遅延読み込み + バンドル分析)
- Next.js 16 フルスタック学習パスと発展方向のまとめ
2. ある DevOps エンジニアの実話
(1) 課題:毎回のデプロイが「地雷を踏む」ようなもの
Diana は Acme Corp の DevOps エンジニアで、TaskFlow のデプロイと運用を担当しています。チームは月に 20 回デプロイしますが、毎回のデプロイが神経をすり減らす体験です:SSH で手動アップロードしたコードで環境変数が欠落し(本番データベースに接続できなくなる)、npm run build はローカルでは動くのにサーバーで OOM(メモリ不足)エラーが発生し、ユーザーエラーはデプロイ後 2 時間経つまで発見されず(監視アラートがない)、Lighthouse スコアが 85 から 62 に下がったことにも誰も気づきませんでした。直近のデプロイでは 30 分の停止が発生し、カスタマーサービスに 200 件以上の苦情が寄せられました。
(2) 自動 CI/CD + デュアルデプロイ + 監視のソリューション
GitHub Actions で品質チェックを自動化 + Vercel でゼロ設定デプロイ + セルフホスト Docker の代替案 + PostHog/Sentry による包括的な監視。
# 1 行の GitHub Actions で完全自動デプロイをトリガー
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npm run build
- uses: amondnet/vercel-action@v20
(3) 成果
| 項目 | 手動デプロイ | CI/CD 自動化 |
|---|---|---|
| デプロイ時間 | 45 分(手動チェック含む) | 8 分(完全自動) |
| 停止リスク | 高い(ファイル欠落/設定誤り) | 低い(一貫性保証) |
| 問題発見 | 2 時間後にユーザーが苦情 | Sentry がリアルタイムアラート |
| Lighthouse スコア | 月 1 回手動実行 | PR ごとに自動検出 |
| ロールバック速度 | 15 分手動復元 | 1 分 Vercel ロールバック |
| チーム効率 | DevOps ボトルネック | セルフサービスデプロイ |
3. Vercel へのデプロイ
(1) Vercel アーキテクチャ
graph TB
subgraph "Vercel エッジネットワーク"
A[グローバル CDN] --> B[エッジ関数]
B --> C[サーバーレス関数]
C --> D[Vercel Postgres]
end
subgraph "GitHub"
E[ソースコード] -->|プッシュ| F[GitHub Actions]
F -->|自動デプロイ| A
end
subgraph "監視"
G[Vercel Analytics]
H[Sentry]
I[PostHog]
end
C --> G
C --> H
B --> I
style A fill:#cce5ff
style E fill:#d4edda
style F fill:#ffeeba
▶ サンプル: vercel.json 設定
キーを含む JSON 設定: framework ("nextjs")、buildCommand、outputDirectory、installCommand、regions (iad1/hkg1/gru1)、env、crons、headers(セキュリティヘッダー + キャッシュルール)、redirects。
{
"framework": "nextjs",
"buildCommand": "npm run build",
"outputDirectory": ".next",
"installCommand": "npm install",
"regions": ["iad1", "hkg1", "gru1"],
"env": {
"NEXT_PUBLIC_APP_URL": "https://taskflow.vercel.app"
},
"crons": [
{
"path": "/api/cron/daily-digest",
"schedule": "0 8 * * *"
}
],
"headers": [
{
"source": "/(.*)",
"headers": [
{
"key": "X-Content-Type-Options",
"value": "nosniff"
},
{
"key": "X-Frame-Options",
"value": "DENY"
},
{
"key": "X-XSS-Protection",
"value": "1; mode=block"
},
{
"key": "Referrer-Policy",
"value": "strict-origin-when-cross-origin"
}
]
},
{
"source": "/uploads/(.*)",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=31536000, immutable"
}
]
}
],
"redirects": [
{
"source": "/app",
"destination": "/dashboard",
"permanent": true
}
]
}
JSON 設定が正常に適用されました。
▶ サンプル: Vercel CLI を使用したデプロイ
コマンドが完了しました。
# 1. Vercel CLI のインストール
npm install -g vercel
# 2. Vercel にログイン
vercel login
# 3. プロジェクトの関連付け
vercel link
# 4. 環境変数の設定
vercel env add DATABASE_URL
vercel env add AUTH_SECRET
vercel env add GOOGLE_CLIENT_ID
vercel env add GOOGLE_CLIENT_SECRET
vercel env add AUTH_URL
vercel env add NEXT_PUBLIC_POSTHOG_KEY
vercel env add SENTRY_DSN
# 5. プレビューデプロイ
vercel
# 6. 本番デプロイ
vercel --prod
# 7. デプロイログの表示
vercel logs
127 パッケージが 3.2 秒で追加されました
38 パッケージが資金提供を求めています
詳細は `npm fund` を実行してください
環境変数が設定されました。
環境変数が設定されました。
環境変数が設定されました。
環境変数が設定されました。
環境変数が設定されました。
環境変数が設定されました。
環境変数が設定されました。
Vercel CLI 28.0.0
? "C:\Users\admin\taskflow" をセットアップしてデプロイしますか? [Y/n] Y
? どのスコープですか? Acme Corp
? 既存のプロジェクトにリンクしますか? いいえ
? プロジェクト名は何ですか? taskflow
? コードはどのディレクトリにありますか? ./
自動検出されたプロジェクト設定 (Next.js):
- ビルドコマンド: npx next build
- 出力ディレクトリ: .next
- Node.js バージョン: 20.x
✅ 本番: https://taskflow.vercel.app [7s]
▶ サンプル: Vercel Postgres への接続
# 1. Postgres データベースの作成
vercel env add DATABASE_URL
# 2. Vercel Postgres SDK のインストール
npm install @vercel/postgres
# 3. Prisma データソースの更新
// prisma/schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
// Vercel Postgres の自動接続プール管理
// Vercel では、マイグレーション競合を避けるために shadowDatabaseUrl を有効にします
}
Prisma スキーマが更新されました。npx prisma generate を実行してクライアントを同期してください。
// src/lib/db.ts
import { PrismaClient } from "@prisma/client"
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined
}
export const prisma = globalForPrisma.prisma ?? new PrismaClient()
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma
4. セルフホスト Docker
(1) セルフホストアーキテクチャ
graph TB
subgraph "Docker ホスト"
A[Nginx<br/>リバースプロキシ] --> B[Node.js アプリ<br/>(PM2)]
B --> C[PostgreSQL<br/>(コンテナ)]
end
subgraph "外部"
D[DNS: taskflow.example.com]
end
D --> A
A -->|SSL 終端| B
style A fill:#d4edda
style B fill:#cce5ff
style C fill:#f8d7da
▶ サンプル: Dockerfile のマルチステージビルド
ビルドコンテキストを Docker デーモンに送信しています 4.096kB
Step 1/8 : FROM node:20-alpine AS deps
---> 1dd67de0f936
...
Successfully built a7b3c2d1e0f9
Successfully tagged taskflow:latest
# ============================================
# ステージ 1: 依存関係
# ============================================
FROM node:20-alpine AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production --ignore-scripts
RUN npm ci --only=development --ignore-scripts
# ============================================
# ステージ 2: ビルド
# ============================================
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED=1
ENV NODE_ENV=production
ENV NEXT_OUTPUT=standalone
RUN npx prisma generate
RUN npm run build
# ============================================
# ステージ 3: 本番ランナー
# ============================================
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
COPY --from=builder /app/prisma ./prisma
COPY --from=builder /app/node_modules/.prisma ./node_modules/.prisma
USER nextjs
EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
CMD ["node", "server.js"]
ビルドコンテキストを Docker デーモンに送信しています 4.096kB
Step 1/8 : FROM node:20-alpine AS deps
---> 1dd67de0f936
...
Successfully built a7b3c2d1e0f9
Successfully tagged taskflow:latest
▶ サンプル: docker-compose.yml
上記の YAML 設定を指定されたファイルパスに保存してください。設定は次回のサーバー再起動時に有効になります。
version: "3.8"
services:
app:
build:
context: .
dockerfile: Dockerfile
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgresql://taskflow:password@db:5432/taskflow
- AUTH_SECRET=${AUTH_SECRET}
- AUTH_URL=https://taskflow.example.com
- GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID}
- GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET}
- NEXTAUTH_URL=https://taskflow.example.com
- NODE_ENV=production
depends_on:
db:
condition: service_healthy
restart: unless-stopped
networks:
- taskflow-network
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/api/health"]
interval: 30s
timeout: 10s
retries: 3
db:
image: postgres:16-alpine
volumes:
- postgres-data:/var/lib/postgresql/data
environment:
- POSTGRES_DB=taskflow
- POSTGRES_USER=taskflow
- POSTGRES_PASSWORD=${DB_PASSWORD}
ports:
- "5432:5432"
restart: unless-stopped
networks:
- taskflow-network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U taskflow"]
interval: 10s
timeout: 5s
retries: 5
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/nginx/ssl:ro
depends_on:
- app
restart: unless-stopped
networks:
- taskflow-network
volumes:
postgres-data:
networks:
taskflow-network:
driver: bridge
設定セクション: version、services、volumes、networks。
▶ サンプル: Nginx 設定
セクション: version、services、volumes、networks。
# nginx.conf
events {
worker_connections 1024;
}
http {
upstream taskflow_app {
server app:3000;
}
server {
listen 80;
server_name taskflow.example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name taskflow.example.com;
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
# セキュリティヘッダー
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Gzip 圧縮
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml text/javascript image/svg+xml;
gzip_min_length 1000;
gzip_comp_level 6;
# 静的アセット(永続キャッシュ)
location /_next/static {
proxy_pass http://taskflow_app;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
expires 365d;
add_header Cache-Control "public, immutable";
}
# アップロード
location /uploads {
proxy_pass http://taskflow_app;
proxy_set_header Host $host;
expires 30d;
add_header Cache-Control "public, immutable";
}
# API ルート(キャッシュなし)
location /api {
proxy_pass http://taskflow_app;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_no_cache 1;
proxy_cache_bypass 1;
}
# その他すべて
location / {
proxy_pass http://taskflow_app;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket サポート
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
}
Nginx 設定が適用されました。リロードして有効化してください。
5. GitHub Actions CI/CD
(1) パイプラインアーキテクチャ
graph LR
A[main にプッシュ] --> B[Lint]
B --> C[型チェック]
C --> D[ユニットテスト]
D --> E[ビルド]
E --> F[Vercel にデプロイ]
F --> G[Lighthouse CI]
G --> H{スコア >= 90?}
H -->|はい| I[成功]
H -->|いいえ| J[ロールバック]
style A fill:#cce5ff
style F fill:#d4edda
style G fill:#ffeeba
style J fill:#f8d7da
6. 本番監視
(1) 監視アーキテクチャ
graph TB
subgraph "ユーザー指標"
A["PostHog<br/>イベント & ファネル"]
B["Vercel Speed Insights<br/>リアルユーザー監視"]
end
subgraph "エラートラッキング"
C["Sentry<br/>エラー & パフォーマンス"]
end
subgraph "パフォーマンス"
D["Lighthouse CI<br/>スコアトレンド"]
E["Bundle Analyzer<br/>バンドルサイズ"]
end
subgraph "アラート"
F["Sentry アラート<br/>PagerDuty"]
G["GitHub チェック<br/>PR コメント"]
end
A --> F
B --> D
C --> F
D --> G
E --> G
style A fill:#d4edda
style B fill:#cce5ff
style C fill:#f8d7da
7. パフォーマンス最適化
(1) 最適化戦略の比較
| 戦略 | 指標 | 最適化前 | 最適化後 | ツール |
|---|---|---|---|---|
| next/image lazy | LCP | 3.2s | 1.5s | <Image loading="lazy"> |
| バンドル分割 | TTI | 4.5s | 2.8s | @next/bundle-analyzer |
| フォント最適化 | CLS | 0.25 | 0.02 | next/font display:swap |
| PPR 静的シェル | FCP | 2.1s | 0.4s | experimental.ppr |
| 画像 CDN | LCP | 2.8s | 1.2s | Vercel Image Optimization |
| キャッシング戦略 | TTFB | 800ms | 120ms | revalidateTag + CDN |
8. 完全な例: 本番環境でのデプロイと検証
# ============================================
# TaskFlow 完全本番デプロイプロセス
# ============================================
# --- プラン A: Vercel デプロイ ---
# 1. ビルド前チェック
npm run lint && npx tsc --noEmit && npm run test -- --run
# 2. ビルドと検証
npm run build
# 出力: ✓ 12.8 秒で正常にコンパイルされました
# 3. プレビューデプロイ
vercel
# 出力: https://taskflow-git-feature-abc123.vercel.app
# 4. E2E テストの実行
npx playwright test --url=https://taskflow-git-feature-abc123.vercel.app
# 5. Lighthouse チェック
npx lhci autorun
# 6. 本番デプロイ
vercel --prod
# 出力: https://taskflow.vercel.app (本番)
# 7. カスタムドメインの設定
vercel domains add taskflow.example.com
# 出力: ✓ ドメイン taskflow.example.com が追加されました
# 8. 検証
curl -I https://taskflow.example.com/api/health
# 出力: HTTP/2 200
# --- プラン B: Docker セルフホスト ---
# 1. Docker イメージのビルド
docker build -t taskflow:latest .
# 2. サービスの起動
docker compose up -d
# 3. データベースマイグレーションの実行
docker compose exec app npx prisma migrate deploy
# 4. シードの実行
docker compose exec app npx prisma db seed
# 5. 検証
curl http://localhost/api/health
# 出力: {"status":"healthy","timestamp":"2026-07-06T10:00:00Z"}
# 6. ログの表示
docker compose logs -f app
# 7. 監視指標
# - PostHog: https://app.posthog.com/project/taskflow
# - Sentry: https://sentry.io/organizations/acme/projects/taskflow
# - Vercel ダッシュボード: https://vercel.com/acme/taskflow
# --- パフォーマンス検証 ---
# Lighthouse レポート
# パフォーマンス: 94/100 ✅
# アクセシビリティ: 97/100 ✅
# ベストプラクティス: 93/100 ✅
# SEO: 100/100 ✅
# Core Web Vitals (RUM)
# LCP: 1.2s ✅ (目標 < 2.5s)
# FID: 12ms ✅ (目標 < 100ms)
# CLS: 0.02 ✅ (目標 < 0.1)
9. 学習パスの振り返りと発展トピック
(1) Next.js 16 フルスタック学習ロードマップ
graph TB
subgraph "フェーズ 1: 基礎 (6 レッスン)"
A1["01-02: 導入 + 環境"]
A2["03-04: ルーティング + レイアウト"]
A3["05-06: ナビゲーション + 並列ルート"]
end
subgraph "フェーズ 2: データ (7 レッスン)"
B1["07-08: RSC + データ収集"]
B2["09-10: SSG/ISR/PPR"]
B3["11-12: キャッシュ + Server Actions"]
B4["13-14: Actions 応用 + API ルート"]
end
subgraph "フェーズ 3: 応用 (6 レッスン)"
C1["15: 認証 Auth.js"]
C2["16: データベース Prisma"]
C3["17: ストリーミング"]
C4["18: 画像/フォント"]
C5["19: SEO/メタデータ"]
C6["20: i18n/ミドルウェア"]
end
subgraph "フェーズ 4: 本番 (6 レッスン)"
D1["21-22: ユニット + E2E テスト"]
D2["23-24: CI/CD + Docker"]
D3["25-26: パフォーマンス + セキュリティ/移行"]
end
subgraph "フェーズ 5: 総合プロジェクト (4 レッスン)"
E1["27: 初期化 + 認証"]
E2["28: ダッシュボード + PPR"]
E3["29: CRUD + Server Actions"]
E4["30: デプロイ + CI/CD + 監視"]
end
A1 --> A2 --> A3 --> B1
B1 --> B2 --> B3 --> B4 --> C1
C1 --> C2 --> C3 --> C4 --> C5 --> C6 --> D1
D1 --> D2 --> D3 --> E1
E1 --> E2 --> E3 --> E4
style A1 fill:#d4edda
style E4 fill:#cce5ff
(2) スキル一覧
| スキル | 対応レッスン | 習熟度 |
|---|---|---|
| App Router ファイルシステムルーティング | #03–#06 | 熟達 |
| サーバーコンポーネント思考 | #07 | 熟達 |
| データ取得とキャッシング | #08–#11 | 熟達 |
| Server Actions CRUD | #12-#13, #29 | 熟達 |
| 認証と認可 | #15, #27 | 習熟 |
| Prisma ORM データベース | #16, #27 | 習熟 |
| テスト(Vitest + Playwright) | #21-#22 | 習熟 |
| CI/CD パイプライン | #23, #30 | 習熟 |
| Vercel + Docker デュアルデプロイ | #24, #30 | 習熟 |
| パフォーマンス監視と最適化 | #25, #30 | 習熟 |
| セキュリティと移行 | #26 | 学習 |
| PPR + キャッシュコンポーネント | #10-#11 | 熟達(独自) |
(3) さらなる学習領域
| トピック | 学習内容 | 推奨リソース |
|---|---|---|
| AI 統合 | Vercel AI SDK + RAG + ストリーミング | vercel/ai (10k⭐) |
| マイクロフロントエンド | Module Federation + Turborepo | Nx / Turborepo 公式 |
| GraphQL | Apollo + Next.js + Codegen | Apollo Client Docs |
| リアルタイムアプリ | WebSocket + Socket.io + Liveblocks | Liveblocks Next.js 統合 |
| エッジコンピューティング | エッジ関数 + Durable Objects | Cloudflare Workers |
| プラットフォームエンジニアリング | Backstage + 内部開発者ポータル | Spotify Backstage |
❓ よくある質問
node_modules などの開発依存関係は含まれません。@latest/next のスタンドアロンモードは、ランタイムに必要な最小限のファイルセット(server.js と .next サブディレクトリ)のみを出力し、node_modules の不要なパッケージを除外します。Docker マルチステージビルドと組み合わせることで、最終イメージには Next.js ランタイムのみが含まれ、サイズと攻撃対象領域が大幅に削減されます。📖 まとめ
- ゼロ設定 Vercel デプロイ:
vercel --prodで 1 コマンドで稼働開始。環境変数、ドメイン、PostgreSQL をサポート。 - セルフホスト Docker の 3 コンテナ: Nginx(リバースプロキシ + SSL)+ Node.js アプリ(PM2 デーモン)+ PostgreSQL(永続化)。
- マルチステージ Dockerfile ビルドは 1.2GB のイメージを 358MB に圧縮し、ランタイムファイルのみを含みます。
- GitHub Actions CI/CD: 品質 → ビルド → デプロイ → lighthouse の 4 ステージ自動パイプライン。
- Lighthouse CI で 90+ のスコアしきい値を設定し、プルリクエストを自動チェックしてパフォーマンス低下を防止。
- PostHog はユーザーイベント(ページビュー、ボタンクリック、コンバージョンファネル)を追跡し、Sentry はランタイムエラーをキャプチャ。
@vercel/speed-insights+@vercel/analyticsゼロ設定でリアルユーザー監視にアクセス。- Bundle Analyzer はバンドルサイズを可視化。目標: 初期 JS < 300 kB。
- TaskFlow は Next.js 16 のフルスタック機能の総合テストです:ルーティング、RSC、PPR、Server Actions から Prisma、Auth.js、CI/CD まで。
📝 練習問題
-
基本問題 (⭐): TaskFlow を Vercel にデプロイし(無料アカウント)、カスタムドメインを設定し(オプション)、
/api/healthが 200 ステータスコードを返すことを確認します。NEXT_PUBLIC_POSTHOG_KEY環境変数を設定し、PostHog がページビューイベントを受信することを確認します。 -
応用問題 (⭐⭐): ローカルでセルフホスト Docker ソリューションをテストします — Docker イメージをビルドし、
docker-compose(App + Nginx + PostgreSQL)を起動し、自己署名 SSL 証明書を設定し、データベースマイグレーションを実行し、HTTPS アクセスを検証します。docker-compose.yml、nginx.conf、Dockerfileをプロジェクトリポジトリにコミットします。 -
発展問題 (⭐⭐⭐): TaskFlow の完全な機能監視を実装します:PostHog で 5 つのキーイベント(task_created / task_completed / user_signup / file_uploaded / project_created)を定義し、属性(所要時間、ブラウザ、地理的位置)を追加する。Sentry でアラートルールを設定する(エラー率が 5% を超えた場合に Slack 通知と PagerDuty アラートを送信)。GitHub Actions に Playwright E2E テストステップを追加する(デプロイ → E2E → Lighthouse を順次実行)。最後に README.md を提出する:デプロイアーキテクチャ図、監視ダッシュボードのスクリーンショット、Lighthouse レポート、CI/CD ステータスバッジを含める。