Tutorial CI/CD dengan GitHub Actions untuk Next.js
ID | EN

Tutorial CI/CD dengan GitHub Actions untuk Next.js

Minggu, 28 Des 2025

CI/CD (Continuous Integration/Continuous Deployment) adalah praktik DevOps yang memungkinkan tim developer untuk mengotomatisasi proses testing dan deployment. Dengan GitHub Actions, kamu bisa setup pipeline yang powerful langsung dari repository GitHub tanpa perlu tools eksternal.

Apa itu CI/CD?

Continuous Integration (CI) adalah praktik dimana setiap perubahan code di-merge ke branch utama secara berkala. Setiap merge memicu automated build dan test untuk mendeteksi bug lebih awal.

Continuous Deployment (CD) adalah ekstensi dari CI dimana setiap perubahan yang lolos testing otomatis di-deploy ke production.

Manfaat CI/CD

  • Deteksi bug lebih cepat
  • Mengurangi manual work
  • Deployment lebih konsisten
  • Feedback loop lebih cepat
  • Meningkatkan confidence saat release

GitHub Actions Basics

GitHub Actions adalah platform CI/CD yang terintegrasi langsung dengan GitHub. Kamu mendefinisikan workflow dalam file YAML di folder .github/workflows/.

Konsep Dasar

  • Workflow: Proses otomatis yang kamu definisikan di repository
  • Event: Trigger yang memulai workflow (push, pull_request, schedule, dll)
  • Job: Sekumpulan steps yang berjalan di runner yang sama
  • Step: Task individual dalam job
  • Action: Reusable unit of code
  • Runner: Server yang menjalankan workflow

Struktur File Workflow

name: CI Pipeline

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

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run a script
        run: echo "Hello, World!"

Setup CI Pipeline untuk Next.js

Mari buat CI pipeline lengkap yang mencakup linting, testing, dan building.

1. Basic CI Workflow

Buat file .github/workflows/ci.yml:

name: CI

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

jobs:
  lint:
    name: Lint
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Run ESLint
        run: npm run lint

  test:
    name: Test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Run tests
        run: npm test

  build:
    name: Build
    runs-on: ubuntu-latest
    needs: [lint, test]
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Build
        run: npm run build

2. Type Checking

Tambahkan type checking untuk TypeScript:

  typecheck:
    name: Type Check
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Type check
        run: npx tsc --noEmit

Deploy ke Vercel

Vercel adalah platform yang dibuat oleh tim Next.js, jadi integrasi deployment sangat seamless.

Cara paling mudah adalah connect repository GitHub ke Vercel. Setiap push akan otomatis trigger deployment.

  1. Buka vercel.com
  2. Import repository dari GitHub
  3. Vercel akan auto-detect Next.js dan configure settings
  4. Done! Setiap push ke main akan deploy ke production

Manual Deployment via GitHub Actions

Jika butuh kontrol lebih, gunakan Vercel CLI:

name: Deploy to Vercel

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      
      - name: Install Vercel CLI
        run: npm i -g vercel@latest
      
      - name: Pull Vercel Environment
        run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
      
      - name: Build Project
        run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}
      
      - name: Deploy to Vercel
        run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }}

env:
  VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
  VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}

Setup Vercel Secrets

  1. Install Vercel CLI: npm i -g vercel
  2. Login: vercel login
  3. Link project: vercel link
  4. Dapatkan token dari Vercel Account Settings
  5. Tambahkan secrets di GitHub repository settings:
    • VERCEL_TOKEN
    • VERCEL_ORG_ID (dari .vercel/project.json)
    • VERCEL_PROJECT_ID (dari .vercel/project.json)

Deploy ke VPS dengan Docker

Untuk yang prefer self-hosting, berikut cara deploy ke VPS menggunakan Docker.

1. Dockerfile untuk Next.js

Buat Dockerfile di root project:

FROM node:20-alpine AS base

FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci

FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED 1
RUN npm run build

FROM base 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

USER nextjs
EXPOSE 3000
ENV PORT 3000

CMD ["node", "server.js"]

Pastikan next.config.js menggunakan standalone output:

/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'standalone',
}

module.exports = nextConfig

2. GitHub Actions untuk Deploy ke VPS

name: Deploy to VPS

on:
  push:
    branches: [main]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Log in to Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      
      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=sha
            type=raw,value=latest
      
      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}

  deploy:
    needs: build-and-push
    runs-on: ubuntu-latest
    
    steps:
      - name: Deploy to VPS
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.VPS_HOST }}
          username: ${{ secrets.VPS_USERNAME }}
          key: ${{ secrets.VPS_SSH_KEY }}
          script: |
            docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
            docker stop nextjs-app || true
            docker rm nextjs-app || true
            docker run -d \
              --name nextjs-app \
              -p 3000:3000 \
              --restart unless-stopped \
              -e DATABASE_URL=${{ secrets.DATABASE_URL }} \
              ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest

3. Setup VPS Secrets

Tambahkan secrets berikut di GitHub:

  • VPS_HOST: IP address atau domain VPS
  • VPS_USERNAME: Username SSH (biasanya root atau user lain)
  • VPS_SSH_KEY: Private key SSH

Generate SSH key:

ssh-keygen -t ed25519 -C "github-actions"

Copy public key ke VPS:

ssh-copy-id -i ~/.ssh/id_ed25519.pub user@your-vps-ip

Environment Secrets

Jangan pernah commit secrets ke repository! Gunakan GitHub Secrets.

Menambahkan Secrets

  1. Buka repository → Settings → Secrets and variables → Actions
  2. Klik “New repository secret”
  3. Masukkan nama dan value

Menggunakan Secrets di Workflow

steps:
  - name: Build with env
    run: npm run build
    env:
      DATABASE_URL: ${{ secrets.DATABASE_URL }}
      NEXT_PUBLIC_API_URL: ${{ secrets.NEXT_PUBLIC_API_URL }}

Environment-Specific Secrets

Untuk secrets berbeda per environment (staging/production):

jobs:
  deploy-staging:
    environment: staging
    steps:
      - run: echo "Deploying to staging"
        env:
          API_URL: ${{ secrets.API_URL }}

  deploy-production:
    environment: production
    needs: deploy-staging
    steps:
      - run: echo "Deploying to production"
        env:
          API_URL: ${{ secrets.API_URL }}

Caching untuk Build Lebih Cepat

Caching dependencies dan build artifacts sangat penting untuk mempercepat CI/CD.

Cache npm Dependencies

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

Cache Next.js Build

- name: Cache Next.js build
  uses: actions/cache@v4
  with:
    path: |
      ~/.npm
      ${{ github.workspace }}/.next/cache
    key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx') }}
    restore-keys: |
      ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-

Cache Docker Layers

- name: Build and push Docker image
  uses: docker/build-push-action@v5
  with:
    context: .
    push: true
    tags: ${{ steps.meta.outputs.tags }}
    cache-from: type=gha
    cache-to: type=gha,mode=max

Matrix Builds

Matrix builds memungkinkan kamu menjalankan job dengan berbagai konfigurasi secara paralel.

Testing Multiple Node Versions

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [18, 20, 22]
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'
      
      - run: npm ci
      - run: npm test

Multiple OS

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

runs-on: ${{ matrix.os }}

Exclude Combinations

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

Deployment Previews

Preview deployments memungkinkan tim untuk mereview perubahan sebelum merge ke production.

Vercel Preview (Automatic)

Jika menggunakan Vercel integration, setiap PR otomatis mendapat preview URL.

Custom Preview dengan Comment

name: Preview Deployment

on:
  pull_request:
    types: [opened, synchronize]

jobs:
  deploy-preview:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Deploy Preview
        id: deploy
        run: |
          # Deploy logic here
          echo "preview_url=https://preview-${{ github.event.pull_request.number }}.example.com" >> $GITHUB_OUTPUT
      
      - name: Comment Preview URL
        uses: actions/github-script@v7
        with:
          script: |
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: '🚀 Preview deployed to: ${{ steps.deploy.outputs.preview_url }}'
            })

Notifications

Kirim notifikasi ke Slack atau Discord saat deployment selesai atau gagal.

Slack Notification

- name: Notify Slack
  if: always()
  uses: 8398a7/action-slack@v3
  with:
    status: ${{ job.status }}
    channel: '#deployments'
    fields: repo,message,commit,author,action,eventName,ref,workflow
  env:
    SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

Discord Notification

- name: Notify Discord
  if: always()
  uses: sarisia/actions-status-discord@v1
  with:
    webhook: ${{ secrets.DISCORD_WEBHOOK }}
    status: ${{ job.status }}
    title: "Deployment"
    description: "Build and deployment finished"
    color: ${{ job.status == 'success' && '0x00ff00' || '0xff0000' }}

Custom Notification Logic

- name: Send Success Notification
  if: success()
  run: |
    curl -X POST ${{ secrets.SLACK_WEBHOOK_URL }} \
      -H 'Content-type: application/json' \
      -d '{"text":"✅ Deployment successful! Commit: ${{ github.sha }}"}'

- name: Send Failure Notification
  if: failure()
  run: |
    curl -X POST ${{ secrets.SLACK_WEBHOOK_URL }} \
      -H 'Content-type: application/json' \
      -d '{"text":"❌ Deployment failed! Check: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"}'

Best Practices

1. Keep Workflows DRY

Gunakan reusable workflows untuk menghindari duplikasi:

# .github/workflows/reusable-build.yml
name: Reusable Build

on:
  workflow_call:
    inputs:
      node-version:
        required: false
        type: string
        default: '20'

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ inputs.node-version }}
          cache: 'npm'
      - run: npm ci
      - run: npm run build

Gunakan di workflow lain:

jobs:
  call-build:
    uses: ./.github/workflows/reusable-build.yml
    with:
      node-version: '20'

2. Use Concurrency

Batalkan workflow sebelumnya jika ada push baru:

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

3. Limit Workflow Runs

on:
  push:
    branches: [main]
    paths:
      - 'src/**'
      - 'package.json'
      - '.github/workflows/**'

4. Use Timeouts

jobs:
  build:
    runs-on: ubuntu-latest
    timeout-minutes: 15

5. Required Status Checks

Di repository settings, set status checks sebagai required sebelum merge ke main.

6. Security Best Practices

  • Gunakan permissions untuk membatasi akses token
  • Pin action versions dengan SHA: uses: actions/checkout@8ade135
  • Review third-party actions sebelum menggunakan
  • Jangan expose secrets di logs
jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

7. Fail Fast

strategy:
  fail-fast: true
  matrix:
    node-version: [18, 20]

Complete CI/CD Workflow

Berikut contoh workflow lengkap yang menggabungkan semua konsep:

name: CI/CD Pipeline

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

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  quality:
    name: Code Quality
    runs-on: ubuntu-latest
    timeout-minutes: 10
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Lint
        run: npm run lint
      
      - name: Type Check
        run: npx tsc --noEmit
      
      - name: Test
        run: npm test -- --coverage
      
      - name: Upload coverage
        uses: codecov/codecov-action@v4
        with:
          token: ${{ secrets.CODECOV_TOKEN }}

  build:
    name: Build
    runs-on: ubuntu-latest
    needs: quality
    timeout-minutes: 15
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      
      - name: Cache Next.js
        uses: actions/cache@v4
        with:
          path: ${{ github.workspace }}/.next/cache
          key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}
      
      - name: Install dependencies
        run: npm ci
      
      - name: Build
        run: npm run build

  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    needs: build
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    environment: production
    timeout-minutes: 10
    
    steps:
      - 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'
      
      - name: Notify Success
        if: success()
        uses: 8398a7/action-slack@v3
        with:
          status: success
          channel: '#deployments'
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
      
      - name: Notify Failure
        if: failure()
        uses: 8398a7/action-slack@v3
        with:
          status: failure
          channel: '#deployments'
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

Kesimpulan

GitHub Actions adalah tool yang powerful untuk mengotomatisasi CI/CD pipeline Next.js. Dengan setup yang tepat, kamu bisa:

  • Menjalankan linting, testing, dan type checking otomatis
  • Deploy ke Vercel dengan zero-config
  • Deploy ke VPS menggunakan Docker
  • Mengamankan secrets dan environment variables
  • Mempercepat build dengan caching
  • Mengirim notifikasi ke tim

Mulai dengan workflow sederhana, lalu iterasi sesuai kebutuhan project. Semoga tutorial ini membantu!