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.
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 folderdist/
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ả foldernode-modules
khổng lồ trong đó, và push tầm 2, hay 3 GB lên registry.
- Với đa số React framework khác, mình luôn làm theo flow:
- 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 filepackage.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.
- 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
- 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
- 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
- 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è.
Hy vọng bài viết sẽ giúp ích cho các bạn! 🎉
All rights reserved