English
Nuxt.js Project GitHub Actions Automated CI/CD Practices (Part 1): Build and Release

Preface
In modern front-end development workflows, automated CI/CD has become standard. However, many developers find it tedious to manually perform the following operations:
Every release requires manually performing the build and packaging.
Build artifacts need to be manually uploaded to Release
Deploying to the server requires manually copying files and restarting the service.
Unable to perceive the execution status of CI/CD
To solve these pain points, this series of articles will guide you from scratch through building a complete automated CI/CD pipeline for Nuxt.js projects using GitHub Actions.
This series of articles is divided into three parts:
| Part | Goal | Expected results |
|---|---|---|
| Part One (Main Text) | Implement automatic build, packaging, and upload to GitHub Release when a Release is created. | After creating a Release, automatically generate tar.gz/zip attachments and automatically update the package.json version number. |
| Part Two | Implement automatic deployment to a remote server | Automatically transfer the build artifacts to the server, restart the service via PM2, and complete deployment. |
| Part Three | Implement CI/CD email notifications | Send an email notification after a successful build and deployment, and issue a timely alert on failure. |
Effect achieved:
When you create a new Release on GitHub, the entire process will run fully automatically:
Automatic code checkout → dependency installation → code inspection → project build
Generate a compressed package and upload it to the Release page.
Build artifacts are automatically transferred to the server
PM2 automatic restart service
Eventually you will receive an email containing the deployment results.
NOTICE
This article is The first part of the article series, focusing on the build and release phases.
Part One Objectives
When you create a new Release on GitHub, automatically:
Code checkout and dependency installation
Code check (Lint + Typecheck)
Nuxt.js project build
Generate archive (tar.gz + zip)
Upload to GitHub Release
Save build artifacts for subsequent deployment use
Automatically commit version number updates
Directory structure
.github/
└── workflows/
└── release-bump.yamlComplete configuration file
yaml
name: Auto Bump Version on Release
on:
release:
types: [created] # Triggered only when a new Release is created
jobs:
# Job 1: Build and Release
build-and-release:
runs-on: ubuntu-latest
permissions:
contents: write # Allow pushing code and creating Releases
outputs:
version: ${{ steps.extract-version.outputs.version }}
project_name: ${{ steps.project-name.outputs.project_name }} # Passed to the subsequent deployment Job
steps:
# 1. Check out the code
- uses: actions/checkout@v6
with:
ref: ${{ github.event.release.target_commitish }}
fetch-depth: 0
# 2. Install pnpm
- name: Install pnpm
uses: pnpm/action-setup@v4
# 3. Set up Node.js
- name: Setup Node.js
uses: actions/setup-node@v5
with:
node-version: 20
cache: 'pnpm'
# 4. Install dependencies
- name: Install dependencies
run: pnpm install --frozen-lockfile
# 5. Code inspection
- name: Lint
run: pnpm run lint
# 6. Type checking
- name: Typecheck
run: pnpm run typecheck
# 7. Extract the version number
- name: Extract Version
id: extract-version
run: |
TAG=${GITHUB_REF#refs/tags/}
VERSION=$(echo "$TAG" | sed 's/^v//')
echo "version=$VERSION" >> $GITHUB_OUTPUT
# 8. Extract project name (dynamically obtained from package.json)
- name: Extract Project Name
id: project-name
run: |
PROJECT_NAME=$(node -p "require('./package.json').name")
echo "project_name=$PROJECT_NAME" >> $GITHUB_OUTPUT
# 9. Update the package.json version number
- name: Update Version
run: |
pnpm version ${{ steps.extract-version.outputs.version }} --no-git-tag-version
cat package.json | grep version
# 10. Build a Nuxt.js project
- name: Build Nuxt.js
env:
NODE_OPTIONS: "--max-old-space-size=4096"
run: pnpm run build
# 11. Create a compressed archive (using a dynamic project name)
- name: Create Distribution Archive
run: |
if [ ! -d ".output" ]; then
echo "Error: .output directory not found!"
exit 1
fi
echo "Contents of .output directory:"
ls -la .output/
PROJECT_NAME="${{ steps.project-name.outputs.project_name }}"
VERSION="${{ steps.extract-version.outputs.version }}"
tar -czf ${PROJECT_NAME}-${VERSION}.tar.gz -C .output .
cd .output && zip -r ../${PROJECT_NAME}-${VERSION}.zip . && cd ..
echo "Archive created successfully!"
ls -la *.tar.gz *.zip
# 12. Upload to GitHub Release
- name: Upload Release Assets
uses: ncipollo/release-action@v1
with:
artifacts: "./${{ steps.project-name.outputs.project_name }}-${{ steps.extract-version.outputs.version }}.tar.gz,./${{ steps.project-name.outputs.project_name }}-${{ steps.extract-version.outputs.version }}.zip"
allowUpdates: true
omitBody: true
omitName: true
token: ${{ secrets.ACCESS_TOKEN }}
makeLatest: "true" # Uploading assets will not break the Release's latest property.
# 13. Upload build artifacts for use by subsequent jobs
- name: Upload Build Artifact
uses: actions/upload-artifact@v7
with:
name: build-output
path: ${{ steps.project-name.outputs.project_name }}-${{ steps.extract-version.outputs.version }}.tar.gz
retention-days: 1
if-no-files-found: error
overwrite: true
# 14. Submit version number change
- name: Commit Changes
run: |
git config user.name "GitHub Actions"
git config user.email "[email protected]"
git add package.json pnpm-lock.yaml
git commit -m "chore(release): bump to ${{ steps.extract-version.outputs.version }} [skip ci]"
git push origin HEAD:${{ github.event.release.target_commitish }}Required Secrets configuration
In the GitHub repository's Settings -> Secrets and variables -> Actions Add to:
| Secret name | Description |
|---|---|
ACCESS_TOKEN | Personal Access Token with write permission (used to upload Release assets) |
💡 Note
default GITHUB_TOKEN In some scenarios, the permissions are insufficient. It is recommended to use a Fine-grained Personal Access Token and grant contents: write and actions: write Permission.
Detailed Explanation of Key Steps
1. Trigger condition
yaml
on:
release:
types: [created]Trigger only when the user manually creates a Release, avoiding triggering a build on every push.
2. Version number extraction
yaml
TAG=${GITHUB_REF#refs/tags/}
VERSION=$(echo "$TAG" | sed 's/^v//')From the Release tag (e.g.v1.0.0)extract the pure version number from(1.0.0).
3. Project Name Extraction
yaml
PROJECT_NAME=$(node -p "require('./package.json').name")From package.json Dynamically read the project name to avoid hardcoding in configuration files, improving the generality and maintainability of the configuration.
💡Extended explanation: extracted project_name will pass through Job's outputs The field is passed to the subsequent deployment Job (Part 2 content). This allows the project name to be dynamically obtained during the deployment phase and used to build the deployment path and file name on the server, enabling data sharing and decoupling between Jobs.
4. Version number update
yaml
pnpm version ${{ steps.extract-version.outputs.version }} --no-git-tag-versionUpdate package.json in the version field, but do not create a git tag (the tag has already been created by Release).
5. Build Optimization
yaml
env:
NODE_OPTIONS: "--max-old-space-size=4096"Allocate 4GB of memory to Node.js to avoid out-of-memory errors when building large projects.
6. Archive creation
yaml
PROJECT_NAME="${{ steps.project-name.outputs.project_name }}"
VERSION="${{ steps.extract-version.outputs.version }}"
tar -czf ${PROJECT_NAME}-${VERSION}.tar.gz -C .output .Packaging .output of the directory content, rather than the directory itself. This way, after extraction you get the files directly, with no need to handle the directory hierarchy additionally.
7. Release Asset Upload and Latest Tagging
yaml
- name: Upload Release Assets
uses: ncipollo/release-action@v1
with:
artifacts: "./${{ steps.project-name.outputs.project_name }}-${{ steps.extract-version.outputs.version }}.tar.gz,./${{ steps.project-name.outputs.project_name }}-${{ steps.extract-version.outputs.version }}.zip"
allowUpdates: true
omitBody: true
omitName: true
token: ${{ secrets.ACCESS_TOKEN }}
makeLatest: "true" # Keep the latest tag for ReleaseParameter description:
| Parameter | Description |
|---|---|
artifacts | Asset file path to upload; separate multiple files with commas. |
allowUpdates | Allow overwriting existing assets with the same name |
omitBody | Do not update the Release description content |
omitName | Do not update the Release name |
token | GitHub Personal Access Token |
makeLatest | Control the latest tag behavior of Release |
makeLatest Available values:
| value | Description |
|---|---|
"true" | Mark this Release as latest (recommended) |
"false" | Do not mark as latest |
"legacy" | Use GitHub's legacy latest logic |
⚠️Precautions: If not explicitly set makeLatest: "true", uploading assets may accidentally break the Release's latest attribute. It is recommended to always add this parameter to ensure that the latest version is correctly marked.
8. Build Artifact Preservation
yaml
- name: Upload Build Artifact
uses: actions/upload-artifact@v7
with:
name: build-output
path: ${{ steps.project-name.outputs.project_name }}-${{ steps.extract-version.outputs.version }}.tar.gzSave the tar.gz file as an artifact for use by subsequent deployment Jobs. Use overwrite: true Avoid name conflicts.
💡 Pitfall Alert
upload-artifact@v4 There is a bug when uploading a directory; suggestions:Upload a file (such as tar.gz) instead of a directory.
Operation process
Create a new Release in the GitHub repository, fill in the version number (e.g.
v0.4.0-alpha11)GitHub Actions automatically trigger workflow
Execute checkout → Install dependencies → Check → Build → Package → Upload
Automatically update the version number and push to the repository.
Downloadable items appear on the Release page
tar.gzandzipAttachment
Frequently Asked Questions
Q:upload-artifact Error "No files found"?
A: Use version v7 and upload.File rather than a directory:
yaml
path: ${{ steps.project-name.outputs.project_name }}-${{ steps.extract-version.outputs.version }}.tar.gzQ: Why is it needed ACCESS_TOKEN?
A: Default GITHUB_TOKEN In some permission scenarios, you cannot upload Release assets, so using a Personal Access Token is more reliable.
Q:fetch-depth: 0 What is the function of?
A: Get the complete git history to ensure the version number commit can be pushed correctly.
Summary
This article completes the first part of the series—automation of building and publishing. By configuring GitHub Actions, we have achieved:
Automatically trigger a build when a Release is created
Automatically generate tar.gz and zip archives
Automatically upload to the Release page
Automatically update the version number in package.json
💡 Confirmation Point
After running this workflow, the Release page should have tar.gz and zip attachments, and in the repository package.json The version number has been updated.
Series navigation
Next article:Nuxt.js Project GitHub Actions Automated CI/CD Practice (Part 2): Remote Deployment- Will explain how to automatically deploy build artifacts to a remote server, covering SCP transfer, SSH command execution, PM2 service restart, and more.