Getting Started with CI/CD for Frontend Projects Using GitHub Actions
This comprehensive guide explains how to set up continuous integration and continuous deployment (CI/CD) for a front‑end Vite‑React‑TypeScript project using GitHub Actions, covering basic concepts, workflow configuration, code linting, testing, release management, deployment to an Nginx server, status badges, email notifications, rollback mechanisms, and end‑to‑end testing.
Introduction
This article introduces CI/CD for front‑end development, focusing on GitHub Actions as the automation tool. It outlines four learning goals: mastering GitHub Actions usage, understanding CI and CD concepts, adding a CI/CD pipeline to a project, and extending the pipeline with rollback, testing, and notifications.
Why Choose GitHub Actions
GitHub Actions is highlighted for its strong support of open‑source projects, free usage for public repositories, and seamless integration with GitHub. Alternatives such as GitLab CI, Travis CI, Drone CI, and Jenkins are compared, noting their limitations for the author’s use case.
GitHub Action Basics (Skip if Familiar)
Key concepts are explained: Event triggers a workflow, Job groups steps and runs on a runner, and Step performs an individual action. An example workflow diagram illustrates the relationship between events, jobs, and steps.
# Specify workflow name
name: learn-github-actions
# Trigger on push events
on: [push]
jobs:
check-bats-version:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: "14"
- run: npm install -g bats
- run: bats -vProject Initialization
A Vite React TypeScript project is created with yarn create vite cicd-study --template react-ts . The resulting UI is shown, and the reader is invited to explore the project structure on StackBlitz.
Adding a CI Process
CI Concept
Continuous Integration (CI) is defined as frequent integration of code into the main branch, requiring automated testing before merging. The GitHub Flow model (feature branch → pull request → review → merge) is used as the basis.
Implementing CI in the Project
Code scanning is set up using @umijs/fabric (ESLint, Prettier, Stylelint). Configuration files .eslintrc.js , .prettierrc.js , and .stylelintrc.js are provided, and lint scripts are added to package.json .
module.exports = {
extends: [require.resolve("@umijs/fabric/dist/eslint")],
}; const fabric = require("@umijs/fabric");
module.exports = {
...fabric.prettier,
}; const fabric = require("@umijs/fabric");
module.exports = {
...fabric.stylelint,
};The test suite uses ts-jest and @testing-library/react with an example App.test.tsx that checks props and button clicks.
import React from "react";
import { render, screen, fireEvent } from "@testing-library/react";
import App from "./App";
test("props is avaliable", () => {
const value = "123";
render(
);
expect(screen.getByRole("props")).toHaveTextContent(value);
});
test("click of button is avaliable", () => {
render(
);
fireEvent.click(screen.getByRole("button"));
expect(screen.getByRole("button")).toHaveTextContent(`count is: 1`);
});CI Workflow Configuration
The CI workflow ( .github/workflows/ci.yml ) runs on pull‑request events to the main branch, checks out the code, sets up Node, caches dependencies, installs packages, runs linting and tests.
name: CI
on:
pull_request:
branches: main
jobs:
CI:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: "16.x"
- name: Cache
id: cache-dependencies
uses: actions/cache@v3
with:
path: |
**/node_modules
key: ${{ runner.OS }}-${{ hashFiles('**/yarn.lock') }}
- name: Installing Dependencies
if: steps.cache-dependencies.outputs.cache-hit != 'true'
run: yarn install
- name: Running Lint
run: yarn lint
- name: Running Test
run: yarn testRunning the workflow shows successful execution, with links to detailed logs and step outputs.
Adding a CD Process
CD Concept
Continuous Delivery (CD) and Continuous Deployment are explained, emphasizing the three steps: build artifacts, deploy to a test environment, and optionally deploy to production. The guide focuses on continuous deployment directly to a production server.
Preparing the Deployment Environment
A server with Nginx is prepared, optionally using Docker Compose. SSH key pairs and a GitHub Personal Access Token are generated and stored as repository secrets ( DEPLOY_TOKEN , PROJECT_ACCESS_TOKEN , REMOTE_HOST , REMOTE_USER ).
version: "3"
services:
pure-nginx:
image: nginx:latest
container_name: pure-nginx
restart: always
volumes:
- /data/www:/usr/share/nginx/html
ports:
- 80:80CD Workflow Configuration
The CD workflow ( .github/workflows/cd.yml ) triggers on pushes to main . It checks out code, sets up Node, caches dependencies, reads the version from package.json , builds the project, creates a GitHub release, uploads the artifact, and finally deploys the artifact to the remote server via SSH.
name: CD
on:
push:
branches: main
jobs:
CD:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: "16.x"
- name: Cache
id: cache-dependencies
uses: actions/cache@v3
with:
path: |
**/node_modules
key: ${{ runner.OS }}-${{ hashFiles('**/yarn.lock') }}
- name: Installing Dependencies
if: steps.cache-dependencies.outputs.cache-hit != 'true'
run: yarn install
- name: Read Version
id: version
uses: ashley-taylor/[email protected]
with:
path: ./package.json
property: version
- name: Building
run: |
yarn build
zip -r assets ./dist/**
- name: Create GitHub Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.PROJECT_ACCESS_TOKEN }}
with:
tag_name: v${{ steps.version.outputs.value }}
release_name: v${{ steps.version.outputs.value }}
draft: false
prerelease: false
- name: Update Release Asset
id: upload-release-asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.PROJECT_ACCESS_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./assets.zip
asset_name: assets.zip
asset_content_type: application/zip
- name: Upload to Deploy Server
uses: easingthemes/[email protected]
env:
SSH_PRIVATE_KEY: ${{ secrets.DEPLOY_TOKEN }}
ARGS: "-avzr --delete"
SOURCE: "dist/"
REMOTE_HOST: ${{ secrets.REMOTE_HOST }}
REMOTE_USER: ${{ secrets.REMOTE_USER }}
TARGET: "/data/www"After merging a pull request, the CD workflow runs, creates a release (e.g., v1.0.0 ), uploads the artifact, and deploys it to the server. The article notes the need to bump the version field for each release to avoid tag conflicts.
Additional Enhancements
Status Badges
Instructions are provided to add CI and CD status badges to the repository README using the standard GitHub badge URL format.

Email Notifications
GitHub account settings can be configured to receive email notifications for workflow failures or for every workflow run.
Rollback Workflow
A manual Rollback workflow ( .github/workflows/rollback.yml ) allows the user to specify a release version and optionally run end‑to‑end tests before redeploying the selected artifact to the server.
name: Rollback
on:
workflow_dispatch:
inputs:
version:
description: "choose a version to deploy"
required: true
E2ETest:
description: "enable to run e2e test"
type: boolean
default: true
jobs:
Rollback:
runs-on: ubuntu-latest
steps:
- name: Echo Version
env:
VERSION: ${{ github.event.inputs.version }}
run: |
echo "Version: $VERSION"
- name: Download Artifact
uses: dsaltares/fetch-gh-release-asset@master
with:
version: "tags/${{ github.event.inputs.version }}"
file: "assets.zip"
token: ${{ secrets.GITHUB_TOKEN }}
- name: Unzip
run: |
unzip assets.zip
ls -a ./dist
- name: Upload to Deploy Server
uses: easingthemes/[email protected]
env:
SSH_PRIVATE_KEY: ${{ secrets.DEPLOY_TOKEN }}
ARGS: "-avzr --delete"
SOURCE: "dist/"
REMOTE_HOST: ${{ secrets.REMOTE_HOST }}
REMOTE_USER: ${{ secrets.REMOTE_USER }}
TARGET: "/data/www"
E2E-Test:
if: ${{ github.event.inputs.E2ETest }} == true
uses: ./.github/workflows/e2e-test.yml
needs: [Rollback]
secrets: inheritEnd‑to‑End (E2E) Testing
E2E tests are built with puppeteer and jest . Configuration ( jest.config.e2e.js ) and a sample test ( src/e2e/App.test.tsx ) are shown. An e2e-test.yml workflow can be triggered manually ( workflow_dispatch ) or called from other workflows ( workflow_call ), running the tests against the deployed server’s public IP.
name: E2E-Test
on: [workflow_call, workflow_dispatch]
jobs:
E2E-Test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: "16.x"
- name: Cache
id: cache-dependencies
uses: actions/cache@v3
with:
path: |
**/node_modules
key: ${{ runner.OS }}-${{ hashFiles('**/yarn.lock') }}
- name: Installing Dependencies
if: steps.cache-dependencies.outputs.cache-hit != 'true'
run: yarn install
- name: Running E2E Test
run: yarn test:e2e --URL=http://${{ secrets.REMOTE_HOST }}/The CD workflow is updated to call the E2E test job after deployment, and the rollback workflow can optionally run the same tests based on the user’s checkbox.
Conclusion
The article wraps up by encouraging readers to star, bookmark, or comment on the post, and provides a final animated GIF.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.