Overview

GitHub Actions provides powerful CI/CD capabilities for Next.js applications. It allows you to automate testing, building, and deployment processes directly from your GitHub repository.

CI/CD Benefits

  • Automated testing - Continuous deployment - Quality gates - Rollback capabilities

GitHub Actions Features

  • Workflow automation - Matrix builds - Environment management - Integration with Vercel

Basic Workflow Setup

Simple CI Workflow

# .github/workflows/ci.yml
name: CI

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

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "18"
          cache: "npm"

      - name: Install dependencies
        run: npm ci

      - name: Run linting
        run: npm run lint

      - name: Run type checking
        run: npm run type-check

      - name: Run tests
        run: npm run test

      - name: Build application
        run: npm run build

Advanced CI Workflow

# .github/workflows/advanced-ci.yml
name: Advanced CI

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

env:
  NODE_VERSION: "18"
  PNPM_VERSION: "8"

jobs:
  lint-and-typecheck:
    name: Lint and Type Check
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

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

      - name: Install dependencies
        run: npm ci

      - name: Run ESLint
        run: npm run lint

      - name: Run TypeScript check
        run: npm run type-check

      - name: Check formatting
        run: npm run format:check

  test:
    name: Test
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [16, 18, 20]

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: "npm"

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm run test:ci

      - name: Upload coverage reports
        uses: codecov/codecov-action@v3
        with:
          file: ./coverage/lcov.info

  build:
    name: Build
    runs-on: ubuntu-latest
    needs: [lint-and-typecheck, test]

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

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

      - name: Install dependencies
        run: npm ci

      - name: Build application
        run: npm run build

      - name: Upload build artifacts
        uses: actions/upload-artifact@v3
        with:
          name: build-files
          path: .next/

Deployment Workflows

Vercel Deployment

# .github/workflows/deploy-vercel.yml
name: Deploy to Vercel

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

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

Multi-Environment Deployment

# .github/workflows/deploy-multi-env.yml
name: Deploy Multi-Environment

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

jobs:
  deploy-staging:
    if: github.ref == 'refs/heads/develop'
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Deploy to Staging
        uses: amondnet/vercel-action@v25
        with:
          vercel-token: ${{ secrets.VERCEL_TOKEN }}
          vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
          vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
          vercel-args: "--target preview"

  deploy-production:
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    needs: [test, build]

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

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

      - name: Notify deployment
        uses: 8398a7/action-slack@v3
        with:
          status: ${{ job.status }}
          channel: "#deployments"
          webhook_url: ${{ secrets.SLACK_WEBHOOK }}

Testing Workflows

Comprehensive Testing

# .github/workflows/test.yml
name: Test

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

jobs:
  unit-tests:
    name: Unit Tests
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "18"
          cache: "npm"

      - name: Install dependencies
        run: npm ci

      - name: Run unit tests
        run: npm run test:unit

      - name: Upload coverage
        uses: codecov/codecov-action@v3

  integration-tests:
    name: Integration Tests
    runs-on: ubuntu-latest

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

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "18"
          cache: "npm"

      - name: Install dependencies
        run: npm ci

      - name: Run integration tests
        run: npm run test:integration
        env:
          DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_db

  e2e-tests:
    name: E2E Tests
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "18"
          cache: "npm"

      - name: Install dependencies
        run: npm ci

      - name: Build application
        run: npm run build

      - name: Start application
        run: npm start &

      - name: Wait for application
        run: npx wait-on http://localhost:3000

      - name: Run E2E tests
        run: npm run test:e2e

Security Workflows

Security Scanning

# .github/workflows/security.yml
name: Security

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  schedule:
    - cron: "0 2 * * 1" # Weekly on Monday at 2 AM

jobs:
  dependency-scan:
    name: Dependency Scan
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Run security audit
        run: npm audit --audit-level moderate

      - name: Check for vulnerabilities
        uses: actions/dependency-review-action@v3
        if: github.event_name == 'pull_request'

  code-scan:
    name: Code Scan
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Run CodeQL Analysis
        uses: github/codeql-action/init@v2
        with:
          languages: javascript

      - name: Perform CodeQL Analysis
        uses: github/codeql-action/analyze@v2

Performance Testing

Performance Workflow

# .github/workflows/performance.yml
name: Performance

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  lighthouse:
    name: Lighthouse CI
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "18"
          cache: "npm"

      - name: Install dependencies
        run: npm ci

      - name: Build application
        run: npm run build

      - name: Start application
        run: npm start &

      - name: Wait for application
        run: npx wait-on http://localhost:3000

      - name: Run Lighthouse CI
        run: npm run lighthouse:ci

      - name: Upload Lighthouse results
        uses: actions/upload-artifact@v3
        with:
          name: lighthouse-results
          path: .lighthouseci/

Advanced Workflows

Matrix Builds

# .github/workflows/matrix.yml
name: Matrix Build

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test-matrix:
    name: Test Matrix
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [16, 18, 20]
        os: [ubuntu-latest, windows-latest, macos-latest]

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: "npm"

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm run test

Conditional Workflows

# .github/workflows/conditional.yml
name: Conditional Workflow

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  check-changes:
    name: Check Changes
    runs-on: ubuntu-latest
    outputs:
      frontend: ${{ steps.changes.outputs.frontend }}
      backend: ${{ steps.changes.outputs.backend }}

    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Check for changes
        uses: dorny/paths-filter@v2
        id: changes
        with:
          filters: |
            frontend:
              - 'src/**'
              - 'app/**'
              - 'components/**'
            backend:
              - 'api/**'
              - 'lib/**'
              - 'server/**'

  test-frontend:
    name: Test Frontend
    runs-on: ubuntu-latest
    needs: check-changes
    if: needs.check-changes.outputs.frontend == 'true'

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "18"
          cache: "npm"

      - name: Install dependencies
        run: npm ci

      - name: Run frontend tests
        run: npm run test:frontend

  test-backend:
    name: Test Backend
    runs-on: ubuntu-latest
    needs: check-changes
    if: needs.check-changes.outputs.backend == 'true'

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "18"
          cache: "npm"

      - name: Install dependencies
        run: npm ci

      - name: Run backend tests
        run: npm run test:backend

Best Practices

Workflow Best Practices

1

Use Matrix Builds

Test across multiple Node.js versions and operating systems
2

Implement Quality Gates

Require tests to pass before deployment
3

Use Caching

Cache dependencies and build artifacts for faster builds
4

Security First

Scan for vulnerabilities and security issues

Performance Optimization

Build Optimization

  • Use npm ci for faster installs - Cache node_modules - Use build artifacts
  • Parallel job execution

Test Optimization

  • Run tests in parallel - Use test coverage - Implement test matrices - Use conditional workflows

Common Patterns

Release Workflow

# .github/workflows/release.yml
name: Release

on:
  push:
    tags:
      - "v*"

jobs:
  release:
    name: Create Release
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "18"
          cache: "npm"

      - name: Install dependencies
        run: npm ci

      - name: Build application
        run: npm run build

      - name: Create release
        uses: actions/create-release@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          tag_name: ${{ github.ref }}
          release_name: Release ${{ github.ref }}
          draft: false
          prerelease: false

Notification Workflow

# .github/workflows/notify.yml
name: Notify

on:
  workflow_run:
    workflows: ["CI", "Deploy"]
    types: [completed]

jobs:
  notify:
    name: Notify Team
    runs-on: ubuntu-latest
    if: github.event.workflow_run.conclusion != 'success'

    steps:
      - name: Notify Slack
        uses: 8398a7/action-slack@v3
        with:
          status: ${{ github.event.workflow_run.conclusion }}
          channel: "#alerts"
          webhook_url: ${{ secrets.SLACK_WEBHOOK }}

Troubleshooting

Key Takeaway: GitHub Actions provides powerful CI/CD capabilities for Next.js applications. Use it to automate testing, building, and deployment while implementing quality gates and security checks.

Overview

Vercel is a cloud platform designed specifically for Next.js applications. It provides automatic deployments, global CDN, serverless functions, and many other features that make it ideal for modern web applications.

Vercel Benefits

  • Automatic deployments - Global CDN - Serverless functions - Edge computing

Key Features

  • Zero-config deployments - Preview deployments - Analytics and monitoring - Environment management

Getting Started with Vercel

Initial Setup

  1. Install Vercel CLI:
    npm i -g vercel
    
  2. Login to Vercel:
    vercel login
    
  3. Deploy your project:
    vercel
    

Project Configuration

Create a vercel.json file in your project root:
{
  "framework": "nextjs",
  "buildCommand": "npm run build",
  "outputDirectory": ".next",
  "installCommand": "npm install",
  "devCommand": "npm run dev",
  "regions": ["iad1"],
  "functions": {
    "app/api/**/*.ts": {
      "maxDuration": 30
    }
  }
}

Environment Variables

Setting Environment Variables

  1. Via Vercel Dashboard:
    • Go to your project settings
    • Navigate to Environment Variables
    • Add your variables
  2. Via CLI:
    vercel env add DATABASE_URL
    vercel env add JWT_SECRET
    
  3. Via vercel.json:
    {
      "env": {
        "DATABASE_URL": "@database-url",
        "JWT_SECRET": "@jwt-secret"
      }
    }
    

Environment-Specific Variables

# Production
vercel env add DATABASE_URL production

# Preview
vercel env add DATABASE_URL preview

# Development
vercel env add DATABASE_URL development

Deployment Strategies

Automatic Deployments

Vercel automatically deploys when you push to your connected Git repository:
# Connect to Git repository
vercel --prod

# Deploy specific branch
vercel --prod --target production

Preview Deployments

Every pull request gets a preview deployment:
# Deploy preview
vercel --target preview

# Deploy specific branch
vercel --target preview --branch feature-branch

Manual Deployments

# Deploy to production
vercel --prod

# Deploy with specific environment
vercel --prod --env production

Vercel-Specific Features

Edge Functions

Create edge functions for global performance:
// app/api/edge/route.ts
import { NextRequest, NextResponse } from "next/server";

export const config = {
  runtime: "edge",
};

export default function handler(request: NextRequest) {
  return NextResponse.json({
    message: "Hello from the edge!",
    region: process.env.VERCEL_REGION,
  });
}

Middleware

Use Vercel’s middleware for request handling:
// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export function middleware(request: NextRequest) {
  // Add custom headers
  const response = NextResponse.next();
  response.headers.set("x-custom-header", "custom-value");

  return response;
}

export const config = {
  matcher: "/api/:path*",
};

Image Optimization

Vercel provides automatic image optimization:
// app/page.tsx
import Image from "next/image";

export default function Home() {
  return (
    <div>
      <Image
        src="/hero-image.jpg"
        alt="Hero image"
        width={800}
        height={600}
        priority
        placeholder="blur"
        blurDataURL="data:image/jpeg;base64,..."
      />
    </div>
  );
}

Performance Optimization

Build Optimization

// vercel.json
{
  "buildCommand": "npm run build",
  "outputDirectory": ".next",
  "installCommand": "npm ci",
  "framework": "nextjs",
  "functions": {
    "app/api/**/*.ts": {
      "maxDuration": 30
    }
  }
}

Caching Strategies

// app/api/cached-data/route.ts
import { NextRequest, NextResponse } from "next/server";

export async function GET(request: NextRequest) {
  const data = await fetchExpensiveData();

  return NextResponse.json(data, {
    headers: {
      "Cache-Control": "public, s-maxage=3600, stale-while-revalidate=86400",
    },
  });
}

Static Generation

// app/blog/[slug]/page.tsx
import { generateStaticParams } from "next";

export async function generateStaticParams() {
  const posts = await fetch("https://api.example.com/posts").then((res) =>
    res.json()
  );

  return posts.map((post: any) => ({
    slug: post.slug,
  }));
}

export default function BlogPost({ params }: { params: { slug: string } }) {
  return <div>Blog post: {params.slug}</div>;
}

Database Integration

Vercel Postgres

// lib/db.ts
import { sql } from "@vercel/postgres";

export async function getUsers() {
  try {
    const { rows } = await sql`SELECT * FROM users`;
    return rows;
  } catch (error) {
    console.error("Database error:", error);
    throw error;
  }
}

Connection Pooling

// lib/db.ts
import { Pool } from "pg";

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  ssl: {
    rejectUnauthorized: false,
  },
});

export async function query(text: string, params?: any[]) {
  const client = await pool.connect();
  try {
    const result = await client.query(text, params);
    return result;
  } finally {
    client.release();
  }
}

Monitoring and Analytics

Vercel Analytics

// app/layout.tsx
import { Analytics } from "@vercel/analytics/react";

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

Custom Analytics

// lib/analytics.ts
export function trackEvent(event: string, properties?: Record<string, any>) {
  if (typeof window !== "undefined") {
    // Send to analytics service
    fetch("/api/analytics", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ event, properties }),
    });
  }
}

Security Best Practices

Environment Variables

// lib/env.ts
import { z } from "zod";

const envSchema = z.object({
  DATABASE_URL: z.string().url(),
  JWT_SECRET: z.string().min(32),
  NEXTAUTH_SECRET: z.string().min(32),
  NEXTAUTH_URL: z.string().url(),
});

export const env = envSchema.parse(process.env);

CORS Configuration

// app/api/cors/route.ts
import { NextRequest, NextResponse } from "next/server";

export async function GET(request: NextRequest) {
  const response = NextResponse.json({ message: "Hello" });

  response.headers.set("Access-Control-Allow-Origin", "https://yourdomain.com");
  response.headers.set(
    "Access-Control-Allow-Methods",
    "GET, POST, PUT, DELETE"
  );
  response.headers.set(
    "Access-Control-Allow-Headers",
    "Content-Type, Authorization"
  );

  return response;
}

Advanced Configuration

Custom Domains

// vercel.json
{
  "domains": ["yourdomain.com", "www.yourdomain.com"],
  "redirects": [
    {
      "source": "/old-path",
      "destination": "/new-path",
      "permanent": true
    }
  ]
}

Headers Configuration

// vercel.json
{
  "headers": [
    {
      "source": "/api/(.*)",
      "headers": [
        {
          "key": "Access-Control-Allow-Origin",
          "value": "https://yourdomain.com"
        },
        {
          "key": "Access-Control-Allow-Methods",
          "value": "GET, POST, PUT, DELETE, OPTIONS"
        }
      ]
    }
  ]
}

Rewrites and Redirects

// vercel.json
{
  "rewrites": [
    {
      "source": "/api/proxy/(.*)",
      "destination": "https://external-api.com/$1"
    }
  ],
  "redirects": [
    {
      "source": "/old-page",
      "destination": "/new-page",
      "permanent": true
    }
  ]
}

Troubleshooting

Common Issues

Debugging

// app/api/debug/route.ts
import { NextRequest, NextResponse } from "next/server";

export async function GET(request: NextRequest) {
  return NextResponse.json({
    environment: process.env.NODE_ENV,
    region: process.env.VERCEL_REGION,
    url: request.url,
    headers: Object.fromEntries(request.headers.entries()),
  });
}

Best Practices

Deployment Best Practices

1

Environment Management

Use different environments for development, staging, and production
2

Security

Never commit secrets to version control, use Vercel’s environment variables
3

Performance

Optimize images, use static generation, implement proper caching
4

Monitoring

Set up analytics and monitoring to track performance and errors

Optimization Tips

Build Optimization

  • Use npm ci for faster installs - Optimize bundle size - Use dynamic imports - Implement code splitting

Runtime Optimization

  • Use edge functions for simple operations - Implement proper caching - Optimize database queries - Use CDN for static assets
Key Takeaway: Vercel provides a powerful platform for deploying Next.js applications with automatic deployments, global CDN, and serverless functions. Use its features to optimize performance and simplify deployment workflows.