404 Not Found

404 Not Found


nginx

Comprehensive Project Deployment: CI/CD Monitoring—TaskFlow’s End-to-End Workflow from Development to Production

From code commit to the user’s browser, there are several steps in between—deployment, CI/CD, monitoring, and performance optimization—and this is the final step in getting your application truly “live.”

1. What You'll Learn


2. A True Story of a DevOps Engineer

(1) Pain Point: Every deployment is like "stepping on a landmine"

Diana is a DevOps engineer at Acme Corp, responsible for the deployment and operations of TaskFlow. The team deploys 20 times a month, but every deployment is a nerve-wracking experience: manually uploading code via SSH resulted in missing environment variables (causing the production database to become unreachable); npm run build—it worked locally but caused an OOM (out of memory) error on the server; user errors weren’t discovered until two hours after deployment (due to a lack of monitoring alerts); and no one noticed that the Lighthouse score dropped from 85 to 62. The most recent deployment caused a 30-minute outage, resulting in over 200 complaints to customer service.

(2) A Solution for Automated CI/CD + Dual-Deployment + Monitoring

Automate quality checks with GitHub Actions + zero-configuration deployment with Vercel + self-hosted Docker alternatives + comprehensive monitoring with PostHog/Sentry.

YAML
# One line GitHub Actions Trigger Fully Automated Deployment
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) Revenue

Dimension Manual Deployment CI/CD Automation
Deployment Time 45 minutes (including manual checks) 8 minutes (fully automated)
Risk of Downtime High (missing files/incorrect configuration) Low (consistency guaranteed)
Issue Detected User complaint 2 hours later Real-time Sentry alert
Lighthouse Score Manually run once a month Automatic PR detection
Rollback Speed 15-minute manual restore 1-minute Vercel Rollback
Team Efficiency DevOps Bottlenecks Self-Service Deployment

3. Deploying to Vercel

(1) Vercel Architecture

100%
graph TB
    subgraph "Vercel Edge Network"
        A[Global CDN] --> B[Edge Functions]
        B --> C[Serverless Functions]
        C --> D[Vercel Postgres]
    end

    subgraph "GitHub"
        E[Source Code] -->|Push| F[GitHub Actions]
        F -->|Auto Deploy| A
    end

    subgraph "Monitoring"
        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

▶ Example: vercel.json configuration

JSON
{
  "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
    }
  ]
}

▶ Example: Deploying with the Vercel CLI

BASH
# 1. Installation Vercel CLI
npm install -g vercel

# 2. Log In Vercel
vercel login

# 3. Related Projects
vercel link

# 4. Set Environment Variables
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. Deployment Preview
vercel

# 6. Deployment Production
vercel --prod

# 7. View the deployment log
vercel logs
TEXT
Vercel CLI 28.0.0
? Set up and deploy "C:\Users\admin\taskflow"? [Y/n] Y
? Which scope? Acme Corp
? Link to existing project? No
? What's your project's name? taskflow
? In which directory is your code? ./
Auto-detected Project Settings (Next.js):
- Build Command: npx next build
- Output Directory: .next
- Node.js Version: 20.x
✅ Production: https://taskflow.vercel.app [7s]

▶ Example: Connecting to Vercel Postgres

BASH
# 1. Create Postgres Database
vercel env add DATABASE_URL

# 2. Installation Vercel Postgres SDK
npm install @vercel/postgres

# 3. Update Prisma Data Sources
PRISMA
// prisma/schema.prisma
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
  // Vercel Postgres Automatic Connection Pool Management
  // On Vercel, enable shadowDatabaseUrl to avoid migration conflicts
}
TYPESCRIPT
// 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. Self-hosted Docker

(1) Self-hosted architecture

100%
graph TB
    subgraph "Docker Host"
        A[Nginx<br/>Reverse Proxy] --> B[Node.js App<br/>(PM2)]
        B --> C[PostgreSQL<br/>(Container)]
    end

    subgraph "External"
        D[DNS: taskflow.example.com]
    end

    D --> A
    A -->|SSL Termination| B

    style A fill:#d4edda
    style B fill:#cce5ff
    style C fill:#f8d7da

▶ Example: Multi-stage builds in Dockerfile

DOCKERFILE
# ============================================
# Stage 1: Dependencies
# ============================================
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

# ============================================
# Stage 2: Build
# ============================================
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

# ============================================
# Stage 3: Production Runner
# ============================================
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"]

▶ Example: 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

▶ Example: Nginx Configuration

NGINX
# 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;

    # Security headers
    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 compression
    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;

    # Static assets (cache forever)
    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";
    }

    # Uploads
    location /uploads {
      proxy_pass http://taskflow_app;
      proxy_set_header Host $host;
      expires 30d;
      add_header Cache-Control "public, immutable";
    }

    # API routes (no cache)
    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;
    }

    # Everything else
    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 support
      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection "upgrade";
    }
  }
}

5. GitHub Actions CI/CD

(1) Pipeline Architecture

100%
graph LR
    A[Push to main] --> B[Lint]
    B --> C[Type Check]
    C --> D[Unit Test]
    D --> E[Build]
    E --> F[Deploy to Vercel]
    F --> G[Lighthouse CI]
    G --> H{Score >= 90?}
    H -->|Yes| I[Success]
    H -->|No| J[Rollback]

    style A fill:#cce5ff
    style F fill:#d4edda
    style G fill:#ffeeba
    style J fill:#f8d7da

▶ Example: CI/CD Workflow

YAML
# .github/workflows/ci-cd.yml
name: CI/CD Pipeline

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]
  workflow_dispatch:

env:
  NODE_VERSION: "20"

jobs:
  quality:
    name: Quality Checks
    runs-on: ubuntu-latest

    services:
      postgres:
        image: postgres:16-alpine
        env:
          POSTGRES_USER: test
          POSTGRES_PASSWORD: test
          POSTGRES_DB: taskflow_test
        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: ${{ env.NODE_VERSION }}
          cache: "npm"

      - name: Install dependencies
        run: npm ci

      - name: Generate Prisma Client
        run: npx prisma generate

      - name: Run database migrations
        run: npx prisma migrate deploy
        env:
          DATABASE_URL: postgresql://test:test@localhost:5432/taskflow_test

      - name: Lint
        run: npm run lint

      - name: Type check
        run: npx tsc --noEmit

      - name: Unit tests
        run: npm run test -- --coverage
        env:
          DATABASE_URL: postgresql://test:test@localhost:5432/taskflow_test
          AUTH_SECRET: test-secret
          AUTH_URL: http://localhost:3000

      - name: Upload coverage
        uses: actions/upload-artifact@v4
        with:
          name: coverage
          path: coverage/

  build:
    name: Build
    needs: quality
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: "npm"

      - name: Install dependencies
        run: npm ci

      - name: Generate Prisma Client
        run: npx prisma generate

      - name: Build
        run: npm run build
        env:
          DATABASE_URL: postgresql://test:test@localhost:5432/taskflow_test
          AUTH_SECRET: build-secret
          AUTH_URL: http://localhost:3000
          NEXT_PUBLIC_POSTHOG_KEY: ${{ secrets.NEXT_PUBLIC_POSTHOG_KEY }}
          SENTRY_DSN: ${{ secrets.SENTRY_DSN }}

      - name: Upload build artifact
        uses: actions/upload-artifact@v4
        with:
          name: build
          path: .next/

  deploy:
    name: Deploy
    needs: build
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Deploy to Vercel
        uses: amondnet/vercel-action@v20
        with:
          vercel-token: ${{ secrets.VERCEL_TOKEN }}
          vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
          vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
          vercel-args: "--prod"
          working-directory: ./

  lighthouse:
    name: Lighthouse CI
    needs: deploy
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Audit URLs with Lighthouse
        uses: treosh/lighthouse-ci-action@v10
        with:
          urls: |
            https://taskflow.vercel.app
            https://taskflow.vercel.app/dashboard
            https://taskflow.vercel.app/dashboard/projects
          budgetPath: ./lighthouse-budget.json
          uploadArtifacts: true
          temporaryPublicStorage: true
        env:
          LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}

      - name: Check Lighthouse Scores
        run: |
          echo "Lighthouse audit complete"
          echo "View results: https://taskflow.vercel.app/lighthouse-report"

▶ Example: Lighthouse Budget Configuration

JSON
{
  "ci": {
    "collect": {
      "numberOfRuns": 3,
      "settings": {
        "preset": "desktop",
        "throttlingMethod": "simulate"
      }
    },
    "assert": {
      "assertions": {
        "categories:performance": ["error", { "minScore": 0.9 }],
        "categories:accessibility": ["error", { "minScore": 0.9 }],
        "categories:best-practices": ["error", { "minScore": 0.9 }],
        "categories:seo": ["error", { "minScore": 0.9 }],
        "categories:pwa": ["warn", { "minScore": 0.5 }],
        "first-contentful-paint": ["error", { "maxNumericValue": 2000 }],
        "largest-contentful-paint": ["error", { "maxNumericValue": 2500 }],
        "cumulative-layout-shift": ["error", { "maxNumericValue": 0.1 }],
        "total-blocking-time": ["error", { "maxNumericValue": 300 }],
        "interactive": ["error", { "maxNumericValue": 3500 }]
      }
    },
    "upload": {
      "target": "temporary-public-storage"
    }
  }
}

6. Production Monitoring

(1) Monitoring Architecture

100%
graph TB
    subgraph "User Metrics"
        A["PostHog<br/>Events & Funnels"]
        B["Vercel Speed Insights<br/>Real User Monitoring"]
    end

    subgraph "Error Tracking"
        C["Sentry<br/>Errors & Performance"]
    end

    subgraph "Performance"
        D["Lighthouse CI<br/>Score Trends"]
        E["Bundle Analyzer<br/>Bundle Size"]
    end

    subgraph "Alerts"
        F["Sentry Alerts<br/>PagerDuty"]
        G["GitHub Checks<br/>PR Comment"]
    end

    A --> F
    B --> D
    C --> F
    D --> G
    E --> G

    style A fill:#d4edda
    style B fill:#cce5ff
    style C fill:#f8d7da

▶ Example: PostHog Event Tracking

TYPESCRIPT
// src/lib/posthog.ts
import { PostHog } from "posthog-node"

export function initPostHog() {
  if (!process.env.NEXT_PUBLIC_POSTHOG_KEY) return null

  return new PostHog(process.env.NEXT_PUBLIC_POSTHOG_KEY, {
    host: process.env.POSTHOG_HOST || "https://app.posthog.com",
  })
}

export async function trackEvent(
  userId: string,
  event: string,
  properties?: Record<string, unknown>
) {
  const client = initPostHog()
  if (!client) return

  client.capture({
    distinctId: userId,
    event,
    properties: {
      ...properties,
      app: "taskflow",
      environment: process.env.NODE_ENV,
    },
  })

  await client.shutdown()
}
TSX
// src/components/providers/posthog-provider.tsx
"use client"

import { usePathname, useSearchParams } from "next/navigation"
import { useEffect } from "react"
import posthog from "posthog-js"
import { PostHogProvider as PHProvider } from "posthog-js/react"

if (typeof window !== "undefined" && process.env.NEXT_PUBLIC_POSTHOG_KEY) {
  posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY, {
    api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST || "https://app.posthog.com",
    capture_pageview: false,
    loaded: (ph) => {
      if (process.env.NODE_ENV !== "production") {
        ph.opt_out_capturing()
      }
    },
  })
}

export function PostHogProvider({ children }: { children: React.ReactNode }) {
  return <PHProvider client={posthog}>{children}</PHProvider>
}

export function PostHogPageView() {
  const pathname = usePathname()
  const searchParams = useSearchParams()

  useEffect(() => {
    if (pathname) {
      let url = window.origin + pathname
      if (searchParams?.toString()) {
        url += `?${searchParams.toString()}`
      }
      posthog.capture("$pageview", { $current_url: url })
    }
  }, [pathname, searchParams])

  return null
}

▶ Example: Sentry Error Tracking

TYPESCRIPT
// src/lib/sentry.ts
import * as Sentry from "@sentry/nextjs"

Sentry.init({
  dsn: process.env.SENTRY_DSN,
  environment: process.env.NODE_ENV,
  tracesSampleRate: process.env.NODE_ENV === "production" ? 0.1 : 1.0,
  replaysSessionSampleRate: 0.1,
  replaysOnErrorSampleRate: 1.0,
  integrations: [
    Sentry.replayIntegration(),
    Sentry.feedbackIntegration({
      colorScheme: "system",
      isEmailRequired: true,
    }),
  ],
})
TYPESCRIPT
// src/instrumentation.ts
export async function register() {
  if (process.env.NEXT_RUNTIME === "nodejs") {
    await import("./lib/sentry")
  }
}

export async function onRequestError(err: unknown, request: unknown, context: unknown) {
  if (process.env.NEXT_RUNTIME === "nodejs") {
    const Sentry = await import("@sentry/nextjs")
    Sentry.captureException(err, { extra: { request, context } })
  }
}

▶ Example: Vercel Speed Insights

TSX
// src/app/layout.tsx Add to
import { SpeedInsights } from "@vercel/speed-insights/next"
import { Analytics } from "@vercel/analytics/react"

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {children}
        <SpeedInsights />
        <Analytics />
      </body>
    </html>
  )
}

7. Performance Optimization

(1) Comparison of Optimization Strategies

Strategy Metrics Before Optimization After Optimization Tools
next/image lazy LCP 3.2s 1.5s <Image loading="lazy">
Bundle Split TTI 4.5s 2.8s @next/bundle-analyzer
Font Optimization CLS 0.25 0.02 next/font display:swap
PPR Static Shell FCP 2.1s 0.4s experimental.ppr
Image CDN LCP 2.8s 1.2s Vercel Image Optimization
Caching strategy TTFB 800ms 120ms revalidateTag + CDN

▶ Example: Lazy Loading of next/image

TSX
// src/components/tasks/task-attachment-image.tsx
import Image from "next/image"

interface TaskAttachmentImageProps {
  src: string
  alt: string
  priority?: boolean
}

export function TaskAttachmentImage({
  src,
  alt,
  priority = false,
}: TaskAttachmentImageProps) {
  return (
    <div className="relative aspect-video overflow-hidden rounded-lg">
      <Image
        src={src}
        alt={alt}
        fill
        sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
        className="object-cover"
        loading={priority ? "eager" : "lazy"}
        priority={priority}
        placeholder="blur"
        blurDataURL="data:image/webp;base64,UklGRi4AAABXRUJQVlA4ICIAAABQAQCdASoIAAgAAwA/JZgC7AAAvAADe4AAf/AAf/A"
      />
    </div>
  )
}

▶ Example: Bundle Analyzer

TYPESCRIPT
// next.config.ts
import type { NextConfig } from "next"

const withBundleAnalyzer = process.env.ANALYZE === "true"
  ? (await import("@next/bundle-analyzer")).default
  : (config: NextConfig) => config

const nextConfig: NextConfig = withBundleAnalyzer({
  experimental: {
    ppr: true,
  },
  images: {
    formats: ["image/avif", "image/webp"],
    deviceSizes: [640, 750, 828, 1080, 1200, 1920],
    remotePatterns: [
      {
        protocol: "https",
        hostname: "api.dicebear.com",
      },
      {
        protocol: "https",
        hostname: "utfs.io",
      },
    ],
  },
  headers: async () => [
    {
      source: "/:path*",
      headers: [
        { key: "X-DNS-Prefetch-Control", value: "on" },
        { key: "Strict-Transport-Security", value: "max-age=63072000; includeSubDomains; preload" },
      ],
    },
  ],
})

export default nextConfig
BASH
# Run locally Bundle Analyzer
ANALYZE=true npm run build
TEXT
Bundle Analysis:
- page (dashboard): 128 kB (initial) / 48 kB (shared)
- page (dashboard/projects/[id]): 145 kB (initial) / 52 kB (shared)
- page (dashboard/tasks): 156 kB (initial) / 62 kB (shared)
- page (auth/signin): 64 kB (initial) / 28 kB (shared)
- Shared modules: @nextui-org/react (12 kB), recharts (18 kB), date-fns (6 kB)
- Total initial JS: 246 kB (target: < 300 kB) ✅

8. Complete Example: Deployment and Validation in a Production Environment

BASH
# ============================================
# TaskFlow Complete Production Deployment Process
# ============================================

# --- Plan A: Vercel Deployment ---

# 1. Pre-Build Checks
npm run lint && npx tsc --noEmit && npm run test -- --run

# 2. Build and Verify
npm run build
# Output: ✓ Compiled successfully in 12.8s

# 3. Deployment Preview
vercel
# Output: https://taskflow-git-feature-abc123.vercel.app

# 4. Run E2E Test
npx playwright test --url=https://taskflow-git-feature-abc123.vercel.app

# 5. Lighthouse Inspection
npx lhci autorun

# 6. Deployment Production
vercel --prod
# Output: https://taskflow.vercel.app (Production)

# 7. Configure a Custom Domain
vercel domains add taskflow.example.com
# Output: ✓ Domain taskflow.example.com added

# 8. Verification
curl -I https://taskflow.example.com/api/health
# Output: HTTP/2 200

# --- Plan B: Docker Self-hosted ---

# 1. Build Docker Image
docker build -t taskflow:latest .

# 2. Start the service
docker compose up -d

# 3. Run the database migration
docker compose exec app npx prisma migrate deploy

# 4. Run Seeding
docker compose exec app npx prisma db seed

# 5. Verification
curl http://localhost/api/health
# Output: {"status":"healthy","timestamp":"2026-07-06T10:00:00Z"}

# 6. View Logs
docker compose logs -f app

# 7. Monitoring Metrics
# - PostHog: https://app.posthog.com/project/taskflow
# - Sentry: https://sentry.io/organizations/acme/projects/taskflow
# - Vercel Dashboard: https://vercel.com/acme/taskflow

# --- Performance Validation ---

# Lighthouse Report
# Performance: 94/100 ✅
# Accessibility: 97/100 ✅
# Best Practices: 93/100 ✅
# SEO: 100/100 ✅

# Core Web Vitals (RUM)
# LCP: 1.2s ✅ (Objective < 2.5s)
# FID: 12ms ✅ (Objective < 100ms)
# CLS: 0.02 ✅ (Objective < 0.1)

9. Review and Advanced Topics in the Learning Path

(1) Next.js 16 Full-Stack Learning Roadmap

100%
graph TB
    subgraph "Phase 1: Basics (6 lessons)"
        A1["01-02: Introduction + Environment"]
        A2["03-04: Routing + Layout"]
        A3["05-06: Navigation + Parallel Routes"]
    end

    subgraph "Phase 2: Data (7 lessons)"
        B1["07-08: RSC + Data Collection"]
        B2["09-10: SSG/ISR/PPR"]
        B3["11-12: Cache + Server Actions"]
        B4["13-14: Actions Advanced + API Routes"]
    end

    subgraph "Phase 3: Advanced (6 lessons)"
        C1["15: Certification Auth.js"]
        C2["16: Database Prisma"]
        C3["17: Streaming"]
        C4["18: Image/Font"]
        C5["19: SEO/Metadata"]
        C6["20: i18n/Middleware"]
    end

    subgraph "Phase 4: Production (6 lessons)"
        D1["21-22: Unit + E2E Test"]
        D2["23-24: CI/CD + Docker"]
        D3["25-26: Performance + Safety/Migration"]
    end

    subgraph "Phase 5: Comprehensive Projects (4 lessons)"
        E1["27: Initialization + Certification"]
        E2["28: Dashboard + PPR"]
        E3["29: CRUD + Server Actions"]
        E4["30: Deployment + CI/CD + Surveillance"]
    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) List of Skills

Skill Corresponding Course Level of Proficiency
App Router File System Routing #03–#06 Mastery
Server Components Mindset #07 Mastery
Data Retrieval and Caching #08–#11 Mastery
Server Actions CRUD #12-#13, #29 Mastery
Certification and Authorization #15, #27 Proficient
Prisma ORM Database #16, #27 Proficient
Testing (Vitest + Playwright) #21-#22 Proficient
CI/CD Pipeline #23, #30 Proficient
Vercel + Docker Dual-Deployment Solution #24, #30 Proficient
Performance Monitoring and Optimization #25, #30 Proficient
Security and Migration #26 Learn
PPR + Cache Components #10-#11 Mastery (Exclusive)

(3) Areas for Further Study

Topic Learning Content Recommended Resources
AI Integration Vercel AI SDK + RAG + Streaming vercel/ai (10k⭐)
Micro Frontends Module Federation + Turborepo Nx / Turborepo Official
GraphQL Apollo + Next.js + Codegen Apollo Client Docs
Real-time Apps WebSocket + Socket.io + Liveblocks Liveblocks Next.js Integration
Edge Computing Edge Functions + Durable Objects Cloudflare Workers
Platform Engineering Backstage + Internal Developer Portal Spotify Backstage

❓ FAQ

Q Which should I choose, Vercel or self-hosted Docker?
A Vercel is suitable for 90% of teams—zero configuration, global CDN, automatic HTTPS, and preview deployments. Self-hosted Docker is only necessary in the following scenarios: compliance requirements (data must remain on-premises), internal systems (no public internet access required), and hyperscale environments (requiring custom infrastructure). TaskFlow recommends Vercel as the primary solution and Docker as a backup.
Q What are appropriate threshold scores for Lighthouse CI in GitHub Actions?
A We recommend Performance ≥ 90, Accessibility ≥ 90, Best Practices ≥ 90, and SEO ≥ 90. If the project has special requirements (such as a large number of third-party scripts causing Performance to drop), you can lower the threshold to 80, but you should add a TODO note explaining the reason and outlining a plan for a fix.
Q Do PostHog and Sentry have overlapping features?
A No. PostHog focuses on user behavior analysis (page views, button clicks, conversion funnels), while Sentry focuses on error tracking (crash stacks, performance spans, release health). The two are complementary: PostHog tells you “what users did,” while Sentry tells you “why things went wrong.”
Q What is the difference between Vercel Postgres and a self-hosted PostgreSQL instance?
A Vercel Postgres is a managed service—it offers automatic backups, automatic scaling, low-latency VPC networking, and a pay-as-you-go pricing model. Self-hosted PostgreSQL gives you full control but requires operations and maintenance (backups, monitoring, and upgrades). TaskFlow uses local Postgres for development, Vercel Postgres for deployment on Vercel, and containerized Postgres for self-hosting with Docker.
Q Why are there three stages in a Docker multi-stage build?
A Stage 1 (deps) installs all dependencies; Stage 2 (builder) builds using only development dependencies; Stage 3 (runner) copies only the artifacts and runtime dependencies. The final image was compressed from 1.2 GB to 358 MB, a 70% reduction. Production containers do not include development dependencies such as the TypeScript compiler, testing tools, or node_modules.
Q What is the purpose of Standalone mode (output: 'standalone')?
A @latest/next's standalone mode outputs only the minimum set of files required for runtime (the server.js and .next subdirectories), excluding unnecessary packages from node_modules. When used with Docker multistage builds, the final image contains only the Next.js runtime, significantly reducing its size and attack surface.
Q After completing the TaskFlow project, how can I continue to improve?
A I recommend three approaches: 1) Add new features to TaskFlow (AI task recommendations, Gantt charts, time tracking); 2) Deploy TaskFlow to a production environment for real-world use; 3) Learn advanced frameworks (tRPC + Next.js for full-stack type safety, Turborepo micro-frontend architecture). Repeat the CI/CD + monitoring process from Phase 5 each time you add a new feature.

📖 Summary

📝 Exercises

  1. Basic Exercise (⭐): Deploy TaskFlow to Vercel (free account), configure a custom domain (optional), and verify that /api/health returns a 200 status code. Configure the NEXT_PUBLIC_POSTHOG_KEY environment variable and confirm that PostHog receives the pageview event.

  2. Advanced Exercise (⭐⭐): Test the self-hosted Docker solution locally—build a Docker image, start docker-compose (App + Nginx + PostgreSQL), configure a self-signed SSL certificate, run database migrations, and verify HTTPS access. Commit docker-compose.yml, nginx.conf, and Dockerfile to the project repository.

  3. Challenge (⭐⭐⭐): Implement full function monitoring for TaskFlow: Define 5 key events in PostHog (task_created / task_completed / user_signup / file_uploaded / project_created) and add attributes (duration, browser, geographic location); Configure alert rules in Sentry (send Slack notifications and PagerDuty alerts when the error rate exceeds 5%); add Playwright E2E test steps to GitHub Actions (Deploy → E2E → Lighthouse in sequence); finally, submit a README.md file containing: a deployment architecture diagram, screenshots of the monitoring dashboard, a Lighthouse report, and a CI/CD status badge.

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏