+2

[DevOps] Deploy Next.js (static only) lên GitHub Pages

Lưu ý rằng: Bài viết này chỉ dành cho những bạn nào không sử dụng API Routes của Next.js, nên phần deploy được sẽ chỉ là trang static web.

image.png

Okay bắt đầu thôi, nhưng... Next.js deploy khác gì với React mà phải lên hẳn một bài riêng thế này.

Thế thì so sánh thử thôi chứ nhỉ? 🤭

1. Deploy Next.js khác gì với React

  • Như chúng ta đã biết, GitHub Actions cùng với GitHub Pages hỗ trợ hosting các trang web static, hay còn gọi là các trang web tĩnh.
  • Bản thân Next.js đã có hỗ trợ official deploy từ nhà Vercel, nhưng vẫn gặp một số các bất cập sau:
    • Bị cold start (ai dùng Serverless thì chắc cũng biết thằng này khó chịu này rồi), khiến đôi khi việc access vào website bị chậm lại vài giây rất khó chịu.
    • Vercel không hỗ trợ routing domain cho nhiều app/project (giả sử như mình có host ở abc.com và muốn api Spring Boot nằm ở abc.com/api/v1 chẳng hạn, thì điều này là bất khả thi với Vercel.
  • Next.js mang tiếng là React-based, nhưng cách dev lẫn cách deploy của em này sẽ có những điểm khác biệt so với React thông thường.
    • Với đa số React framework khác, mình luôn làm theo flow: npm install - chạy build script - mang folder dist/ xách vào /usr/share/nginx/html.
    • Thế nhưng, thằng Next.Js bản thân nó là server-side, render từ backend, nên lệnh build lại ra cả frontend và backend, và khi start lên bằng next start thì phải chạy dính cả node-modules, điều này gây ra tới hệ lụy là khi build Docker image cho Next, phải dính luôn cả folder node-modules khổng lồ trong đó, và push tầm 2, hay 3 GB lên registry.
  • Vì thế solution là sẽ export các component, static, public,... từ backend của Next ra folder build.

2. Deploy Next.js cho từng phiên bản

  • Do phạm vi của bài viết không bao gồm GitHub Actions nên mình sẽ không đi sâu vào giải thích các khái niệm trong workflows. Có lẽ sẽ để dành cho một series nào đó về GitHub Actions.
  • Ấy ấy, trước khi tiếp tục thì giúp mình check phiên bản Next.js trong file package.json của mọi người rồi tiếp tục nhé.

2.1. Với những version Next .js mới hơn v13.3.0

Phiên bản này có hỗ trợ export thẳng các static assets bằng cách config next.config.js, lưu ý rằng với các phiên bản mới, lệnh next export sẽ không sử dụng được mà chỉ có thể config từ trước.

  1. Trong file next.config.js, các bạn thêm vào config "output": "export".
/**
 * @type {import('next').NextConfig}
 */
const nextConfig = {
  output: 'export',
}
 
module.exports = nextConfig
  1. Làm thế này, mỗi khi chạy lệnh build, ta sẽ luôn nhận được một folder ./out/ có cả static files.
next build
  1. Sau đó ta ở root folder của repo GitHub, ta thêm file ở đường dẫn .github/workflows/nextjs.yml với nội dung như sau:
name: Deploy Next.Js to GitHub Pages

on:
  push:
    branches: [main, master]
  workflow_dispatch:

permissions:
  contents: read
  pages: write
  id-token: write

concurrency:
  group: "pages"
  cancel-in-progress: false

env:
  PROJECT_DIR: .  # Set the root folder here

jobs:
  # Build job
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Detect package manager
        id: detect-package-manager
        run: |
          cd ${{ env.PROJECT_DIR }}
          if [ -f "yarn.lock" ]; then
            echo "manager=yarn" >> $GITHUB_OUTPUT
            echo "install-command=install" >> $GITHUB_OUTPUT
            echo "run-command=yarn" >> $GITHUB_OUTPUT
          elif [ -f "package-lock.json" ]; then
            echo "manager=npm" >> $GITHUB_OUTPUT
            echo "install-command=install --force --legacy-peer-deps" >> $GITHUB_OUTPUT
            echo "run-command=npm" >> $GITHUB_OUTPUT
          elif [ -f "bun.lockb" ]; then
            echo "manager=bun" >> $GITHUB_OUTPUT
            echo "install-command=install" >> $GITHUB_OUTPUT
            echo "run-command=bun" >> $GITHUB_OUTPUT
          elif [ -f "pnpm-lock.yaml" ]; then
            echo "manager=pnpm" >> $GITHUB_OUTPUT
            echo "install-command=install" >> $GITHUB_OUTPUT
            echo "run-command=pnpm" >> $GITHUB_OUTPUT
          else
            echo "Unable to determine package manager"
            exit 1
          fi

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: ${{ steps.detect-package-manager.outputs.manager }}

      - name: Setup Pages
        uses: actions/configure-pages@v5
        with:
          static_site_generator: next

      - name: Restore cache
        uses: actions/cache@v4
        with:
          path: |
            ${{ env.PROJECT_DIR }}/.next/cache
          key: ${{ runner.os }}-nextjs-${{ hashFiles('${{ env.PROJECT_DIR }}/**/package-lock.json', '${{ env.PROJECT_DIR }}/**/yarn.lock', '${{ env.PROJECT_DIR }}/**/bun.lockb') }}-${{ hashFiles('${{ env.PROJECT_DIR }}/**.[jt]s', '${{ env.PROJECT_DIR }}/**.[jt]sx') }}
          restore-keys: |
            ${{ runner.os }}-nextjs-${{ hashFiles('${{ env.PROJECT_DIR }}/**/package-lock.json', '${{ env.PROJECT_DIR }}/**/yarn.lock', '${{ env.PROJECT_DIR }}/**/bun.lockb') }}-

      - name: Install dependencies
        run: ${{ steps.detect-package-manager.outputs.run-command }} ${{ steps.detect-package-manager.outputs.install-command }}
        working-directory: ${{ env.PROJECT_DIR }}

      - name: Build with Next.js
        run: ${{ steps.detect-package-manager.outputs.run-command }} next build
        working-directory: ${{ env.PROJECT_DIR }}

      - name: Upload artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: ${{ env.PROJECT_DIR }}/out

  # Deployment job
  deploy:
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    runs-on: ubuntu-latest
    needs: build
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4
  1. Push lên và đợi CI workflow chạy thôi là xong.

2.2. Với những bạn dùng những phiên bản Next.js cũ hơn v13.3.0

Cái này mì ăn liền hơn những phiên bản mới, vì những phiên bản cũ có lệnh next export nên ta sẽ dùng trực tiếp lệnh này luôn, ngay sau next build.

Tạo file .github/workflows/nextjs.yml với nội dung bên dưới và chỉnh sửa PROJECT_DIR ở mục env phù hợp với codebase của mọi người nhé.

name: Deploy frontend to GitHub Pages

on:
  push:
    branches: [main, master]
  workflow_dispatch:

permissions:
  contents: read
  pages: write
  id-token: write

concurrency:
  group: "pages"
  cancel-in-progress: false

env:
  PROJECT_DIR: .  # Set the root folder here

jobs:
  # Build job
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Detect package manager
        id: detect-package-manager
        run: |
          cd ${{ env.PROJECT_DIR }}
          if [ -f "yarn.lock" ]; then
            echo "manager=yarn" >> $GITHUB_OUTPUT
            echo "install-command=install" >> $GITHUB_OUTPUT
            echo "run-command=yarn" >> $GITHUB_OUTPUT
          elif [ -f "package-lock.json" ]; then
            echo "manager=npm" >> $GITHUB_OUTPUT
            echo "install-command=install --force --legacy-peer-deps" >> $GITHUB_OUTPUT
            echo "run-command=npm" >> $GITHUB_OUTPUT
          elif [ -f "bun.lockb" ]; then
            echo "manager=bun" >> $GITHUB_OUTPUT
            echo "install-command=install" >> $GITHUB_OUTPUT
            echo "run-command=bun" >> $GITHUB_OUTPUT
          elif [ -f "pnpm-lock.yaml" ]; then
            echo "manager=pnpm" >> $GITHUB_OUTPUT
            echo "install-command=install" >> $GITHUB_OUTPUT
            echo "run-command=pnpm" >> $GITHUB_OUTPUT
          else
            echo "Unable to determine package manager"
            exit 1
          fi

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: ${{ steps.detect-package-manager.outputs.manager }}

      - name: Setup Pages
        uses: actions/configure-pages@v5
        with:
          static_site_generator: next

      - name: Restore cache
        uses: actions/cache@v4
        with:
          path: |
            ${{ env.PROJECT_DIR }}/.next/cache
          key: ${{ runner.os }}-nextjs-${{ hashFiles('${{ env.PROJECT_DIR }}/**/package-lock.json', '${{ env.PROJECT_DIR }}/**/yarn.lock', '${{ env.PROJECT_DIR }}/**/bun.lockb') }}-${{ hashFiles('${{ env.PROJECT_DIR }}/**.[jt]s', '${{ env.PROJECT_DIR }}/**.[jt]sx') }}
          restore-keys: |
            ${{ runner.os }}-nextjs-${{ hashFiles('${{ env.PROJECT_DIR }}/**/package-lock.json', '${{ env.PROJECT_DIR }}/**/yarn.lock', '${{ env.PROJECT_DIR }}/**/bun.lockb') }}-

      - name: Install dependencies
        run: ${{ steps.detect-package-manager.outputs.run-command }} ${{ steps.detect-package-manager.outputs.install-command }}
        working-directory: ${{ env.PROJECT_DIR }}

      - name: Build with Next.js
        run: ${{ steps.detect-package-manager.outputs.run-command }} next build
        working-directory: ${{ env.PROJECT_DIR }}

      - name: Export with Next.js
        run: ${{ steps.detect-package-manager.outputs.run-command }} next export
        working-directory: ${{ env.PROJECT_DIR }}

      - name: Upload artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: ${{ env.PROJECT_DIR }}/out

  # Deployment job
  deploy:
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    runs-on: ubuntu-latest
    needs: build
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4

Okay thế là xong rồi, nếu CI chạy lỗi ở lần đầu, có thể là do repo GitHub của các bạn chưa mở GitHub Pages, chỉ cần vào Settings - Pages - Build and development và chọn GitHub Actions như hình là xong thôi nè.

image.png

Hy vọng bài viết sẽ giúp ích cho các bạn! 🎉


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí