들어가며
현재 배포하고자 하는 프로젝트는 다음과 같다.
https://github.com/guesung/javascript-lotto/tree/step-2
배포하려는 파일은 하나이다. index.html
// index.html
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<title>🎱 행운의 로또</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link rel="stylesheet" href="./src/web/css/main.css" />
</head>
<body>
<div id="app"></div>
<script type="module" src="./src/step2-index.js"></script>
</body>
</html>
위 파일을 GitHub Pages를 활용해 배포해보자.
개념
Vite
왜 vite일까. vite를 이용하면 뭐가 좋을까?
vite는 모던 프론트엔드 개발을 위한 빌드 도구로, 빠른 개발 서버 시작과 즉각적인 HMR(Hot Module Replacement)을 제공한다. 개발 시에는 번들링 과정 없이 네이티브 ES 모듈을 직접 사용해 빠른 개발 경험을 제공하고, 프로덕션 빌드 시에는 최적화된 번들링을 수행한다.
vite를 이용하면 다음과 같은 이점을 얻을 수 있다.
- 빠른 개발 환경을 통해 생산성을 높일 수 있다.
- 빌드 시 최적화된 번들링을 자동으로 수행해주어 배포에 용이하다.
- ES 모듈을 기본적으로 지원하여 모던 자바스크립트 개발 경험을 제공한다.
GitHub Pages
GitHub Pages는 저장소의 HTML, CSS, JavaScript 파일을 웹사이트로 직접 배포해주는 정적 호스팅 서비스다. 프로젝트 문서나 포트폴리오 사이트를 무료로 호스팅할 수 있고, username.github.io 형식의 도메인을 자동으로 제공하며 커스텀 도메인 설정도 가능하다.
GitHub Actions
GitHub Actions는 push, pull request 같은 저장소 이벤트를 기반으로 워크플로우를 자동 실행하는 CI/CD(지속적 통합/지속적 배포) 도구다. 코드 테스트, 빌드, 배포를 자동화할 수 있고, GitHub Pages와 연동하면 정적 웹사이트를 자동으로 배포할 수 있다.
실전
deploy.yml 구성
기본적인 yml은 다음과 같다.
# GitHub Pages에 정적 콘텐츠를 배포하기 위한 간단한 워크플로우
name: Deploy static content to Pages
on:
# 기본 브랜치에 대한 푸시 이벤트 발생 시 실행
push:
branches: ['main']
# Actions 탭에서 수동으로 워크플로우를 실행할 수 있도록 구성
workflow_dispatch:
# GITHUB_TOKEN의 권한을 설정하여 GitHub Pages에 배포할 수 있도록 함
permissions:
contents: read
pages: write
id-token: write
# 동시에 하나의 배포만 허용하도록 구성
concurrency:
group: 'pages'
cancel-in-progress: true
jobs:
# 단순히 배포만 수행하기에 하나의 잡으로만 구성
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Setup Pages
uses: actions/configure-pages@v4
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
# dist 디렉터리 업로드
path: './dist'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
만약 위를 실행했을 때 에러가 발생했다면 @Not found 를 참고바란다. 결론부터 이야기하면 Settings > Environment에서 github-pages의 브랜치를 수정해야한다.
이제 옵션을 하나씩 살펴보자.
1) workflows
GitHub Actions는 .github/workflows폴더에 작성한다. 하나의 파일은 하나의 워크플로우가 된다.
# 워크플로우의 이름을 지정한다
name: Deploy static content to Pages
on:
# 기본 브랜치에 대한 푸시 이벤트 발생 시 실행
push:
branches: ['main']
# Actions 탭에서 수동으로 워크플로우를 실행할 수 있도록 구성
workflow_dispatch:
# GITHUB_TOKEN의 권한을 설정하여 GitHub Pages에 배포할 수 있도록 함
# GitHub Pages에 배포하기 위해서는 pages, id-token에 대한 write 권한이 필요하다
permissions:
contents: read
pages: write
id-token: write
# 동시에 하나의 배포만 허용하도록 구성
# 'pages'그룹 내에서 하나의 워크플로우만 실행되도록 설정해 중복 배포를 방지한다
concurrency:
group: 'pages'
cancel-in-progress: true
위에서 동시성(concurrency)을 설정할 경우, 아래와 같이 더 새로운 워크플로우가 시작할 경우 기존 워크플로우를 중단한다.
2) jobs
하나의 워크플로우는 여러 개의 작업으로 구성될 수 있다. 이번에는 배포만할 것이기에 deploy하나의 작업으로만 구성되어 있다.
jobs:
deploy:
# GitHub Actions의 배포 환경을 설정한다
# name은 환경의 이름, url은 배포된 페이지의 URL을 출력한다
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
# 워크플로우가 실행될 환경을 지정한다
# 'ubuntu-latest'는 가장 최신 버전의 Ubuntu 환경에서 작업을 실행한다는 의미이다
runs-on: ubuntu-latest
3) Steps
하나의 job은 여러 개의 Step으로 구성되어 있으며, job 안의 step들은 같은 작업 환경을 공유한다.
# ..
steps:
# 리포지토리의 코드를 가져와 워크플로우 환경에서 사용할 수 있도록 한다
- name: Checkout
uses: actions/checkout@v4
# Node.js 환경을 설정한다. 여기서는 Node.js 20 버전을 사용하고 npm 캐시를 활성화한다
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
# 프로젝트의 의존성을 설치한다
# npm ci는 package-lock.json 파일을 기반으로 정확히 동일한 버전의 패키지를 설치한다
- name: Install dependencies
run: npm ci
# 프로젝트를 빌드한다
# 현재 프로젝트의 경우 npm run build 명령으로 dist 디렉토리에 정적 파일들을 생성한다
- name: Build
run: npm run build
# GitHub Actions를 위한 GitHub Pages 설정을 구성한다
# GitHub Pages 서비스를 활성화하고 필요한 설정을 자동으로 적용한다
- name: Setup Pages
uses: actions/configure-pages@v4
# 빌드된 파일을 GitHub Pages에 업로드한다
# 여기서는 './dist' 경로에 있는 파일들이 GitHub Pages에 배포될 정적 콘텐츠로 지정된다
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
# dist 디렉터리 업로드
path: './dist'
# 최종적으로 GitHub Pages에 배포하는 단계이다
# 이 단계에서 실제로 사이트가 배포되며, 배포 URL이 출력된다
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
심화 : 의존성 캐싱
캐싱
# ..
# npm 의존성(node_modules)을 캐싱하여 배포 성능을 향상시킨다
- name: Use Cache
uses: actions/cache@v3
id: cache
with:
# node_modules 폴더를 캐싱 대상으로 지정
path: '**/node_modules'
# package-lock.json 파일의 해시값을 기반으로 캐시 키를 생성
# 이 파일이 변경되면 새로운 캐시가 생성된다
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
# 정확한 캐시 키가 없을 경우 사용할 대체 키
# 부분적인 캐시 히트로 일부 의존성만 다시 설치하도록 한다
restore-keys: |
${{ runner.os }}-node-
프로젝트 내 package-lock.json 파일을 기반으로 캐시 키를 생성해 의존성을 관리한다. 만약 이전 workflow에서 캐싱한 의존성(node_modules)가 이번 workflow와 동일하다면, package-lock.json 또한 동일할 것이고 새롭게 의존성을 설치할 필요가 없기 때문이다.
캐시 사용하기
# ..
# 프로젝트의 의존성 패키지들을 설치한다
- name: Install dependencies
# npm ci 명령은 package-lock.json을 기반으로 정확한 버전의 패키지를 설치한다
run: npm ci
# 캐시 히트가 있는 경우에는 이 단계를 건너뛴다
# 이미 node_modules가 캐시에서 복원되었으므로 다시 설치할 필요가 없다
if: steps.cache.outputs.cache-hit != 'true'
위 캐시 설정을 적용하면 node_modules 디렉토리가 캐싱되어 반복되는 워크플로우 실행에서 의존성 설치 시간을 크게 단축할 수 있다. 캐시 히트가 발생하면 package-lock.json이 변경되지 않은 경우에 이전에 저장된 node_modules를 재사용하므로 npm ci 단계를 건너뛰게 되어 전체 워크플로우 실행 시간이 단축된다.
- 캐시를 사용하지 않는 경우
- 캐시를 사용하는 경우
CI
CI는 Continuous Integration의 약자로, 여러 개발자가 코드를 지속적으로 통합하고 테스트하는 소프트웨어 개발 방식이다. GitHub Actions를 사용하면 아래와 같이 빌드, 린트, 타입 체크, E2E 테스트 등 다양한 검증 단계를 자동화할 수 있다.
# ..
# 코드 품질 검사 실행
- name: Lint
run: npm run lint
# TypeScript 타입 검사 실행
- name: Type Check
run: npm run type-check
# 프로젝트 빌드 실행
- name: Build
run: npm run build
# Cypress를 사용한 End-to-End 테스트 실행
- name: E2E
uses: cypress-io/github-action@v6
env:
BASE_URL: ${{ secrets.BASE_URL }} # 환경 변수로 기본 URL 설정 (시크릿에서 가져옴)
with:
start: npm run preview # 테스트를 위한 프리뷰 서버 시작
자세한 코드는 아래 참고 바란다.
트러블 슈팅
Untitled
Untitled
Untitled
맺으며
이번 글에서는 간단한 HTML 파일 하나를 GitHub Pages를 통해 배포하는 과정을 살펴보았다. 특히 Vite를 활용한 배포 방법과 GitHub Actions를 통한 자동화 배포에 중점을 두었으며, 실제 과정에서 발생할 수 있는 여러 문제와 그 해결책까지 함께 공유했다.
코드 짜는 것만큼 배포도 중요하다는 걸 이번에 체감했다.
특히 의존성 캐싱을 통한 배포 성능 향상, 환경 설정에 따른 배포 제어, 그리고 경로 문제 해결을 위한 base 옵션 설정 등은 실무에서 자주 마주치는 문제들이기에 이런 삽질 경험이 나중에 도움이 될 것 같다.
앞으로도 자동화를 적극 써보려 한다.