와.. 이걸 내가 해냄ㅠㅠ

깃허브에서 서비스와 스토리북을 동시에 배포하고자 했다.

처음에는 깃허브 액션(GitHub Actions) 스크립트를 두 개로 나눠서 각각 진행하려고 했지만...

결론: 한 개의 스크립트에서 모든 배포를 진행해야 한다.

기본적으로 알아야 할 GitHub Pages의 URL 구조

GitHub Pages의 기본 URL은 아래와 같은 구조를 가진다:

https://<username>.github.io/<repository-name>/

여기서 나는 다음과 같은 요구사항이 있었다:

  • / 하위에는 서비스를 배포
  • /storybook 하위에는 스토리북을 배포
  • 심지어 SPA(Single Page Application)의 특성상 새로고침 시에도 404가 뜨지 않게 처리해야 한다.

이렇게 설정하려면 단순히 두 개의 배포 스크립트를 작성한다고 끝나는 게 아니다.

GitHub Pages와 GitHub Actions의 특성을 이해하고, 배포 과정에서 파일 충돌과 404 문제를 해결해야 한다.

주요 문제와 해결 과정

문제 1: 여러 경로를 하나의 프로젝트로 배포

처음에는 배포 스크립트를 두 개로 나눠서 각각 React 서비스와 Storybook을 배포했다.

하지만 두 작업이 같은 gh-pages 브랜치를 덮어쓰기 때문에, 마지막에 실행된 배포만 살아남았다.

해결: 하나의 스크립트로 통합

GitHub Actions에서 keep_files: true 옵션을 사용하여 기존 파일을 유지하면서, 서비스와 스토리북을 각각 다른 디렉토리에 배포하도록 설정했다.


문제 2: SPA 새로고침 시 404 문제

React는 SPA(Single Page Application)이기 때문에, 브라우저 새로고침 시 index.html로 리다이렉트되지 않으면 404가 발생한다.

해결: 404.html에 리다이렉트 스크립트 추가

GitHub Pages는 404 요청을 404.html로 처리할 수 있다. 이를 이용해 모든 잘못된 요청을 React의 index.html로 리다이렉트하거나, /storybook 요청은 Storybook으로 리다이렉트하도록 스크립트를 작성했다.

스크립트

저장소와 환경설정

storybook과 서비스를 배포하기 전에 공통적으로 진행하는 부분이다

베이스로 하는 브랜치 코드를 가져와 의존성을 설치하고 캐싱을 진행해 최적화한다.

      - name: Checkout Repository
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 18
          cache: 'npm'
          cache-dependency-path: '**/package-lock.json'

      - name: Install dependencies
        run: npm ci

Base 경로 설정

github의 환경변수 설정을 하고 시스템에서 사용할 환경변수에 넣어준다. (환경변수를 안쓰면 건너뛰어도 된다)

 - name: Setup environment variables
        run: |
          echo "REACT_APP_BASE_URL=${{ secrets.REACT_APP_BASE_URL }}" >> .env
          PUBLIC_URL=$(echo $GITHUB_REPOSITORY | sed -r 's/^.+\/(.+)$/\/\1\//')
          echo PUBLIC_URL=$PUBLIC_URL > .env

빌드

각 애플리케이션을 빌드하여 dist 디렉토리와 storybook-static 디렉토리에 결과물을 생성한다.

      - name: Build React App
        run: npm run build
      - name: Build Storybook
        run: npm run build-storybook

404 경로 처리

GitHub Pages에서 React 앱과 Storybook 간 경로 충돌 문제를 해결하기 위해 404.html에 스크립트를 추가한다. myrok_client 는 나의 레포지토리 이름이기 때문에 이곳에 본인의 레포 이름을 넣는다.

 			- name: Create 404.html for GitHub Pages
        run: |
          cp dist/index.html dist/404.html
          echo '
          <script>
            if (location.pathname.includes("/storybook")) {
              location.href = "/myrok_client/storybook/";
            } 
          </script>
          ' >> dist/404.html

배포

React 앱의 빌드 결과물을 GitHub Pages에 업로드한다. 이때 publish_dir는 어떤 dir에 있는 파일을 배포할건지이다. 이건 루트경로에 배포된다.keep_files: true로 설정하여 Storybook 파일을 덮어쓰지 않도록 보장하자.

      - name: Upload to GitHub Pages
        uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./dist
          destination_dir: .
          keep_files: true

그리고 Storybook의 빌드 결과물을 GitHub Pages에 업로드한다.

      - name: Upload Storybook to GitHub Pages
        uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./storybook-static
          destination_dir: storybook
          keep_files: true

이러면 경로별로 여러 서비스 배포할 수 있다!!

당연하지만 주의할 점으론 서비스의 페이지 경로가 스토리북이 있는 경로와 겹치지 않도록한다.

최종코드

위 코드를 잘 조합한 결과이다..! 진짜진짜 이거 성공시킨다고 커밋을 80개는 넘게 했다.

여러분은 고생하지 마시길… 아래 링크는 내가 배포 성공한 결과물이다.

다만 이거 학교 플젝이라 혼자 프론트 다 개발하느라 반응형? 개나줘버려! 버그? 흐린 눈 해! 라는 마인드로 개발해서 결과물은 처참하다ㅋㅋㅋ

그냥 배포가 된다는 것만.. 확인하셔요

본 서비스: https://myrokkk.github.io/myrok_client/

스토리북 : https://myrokkk.github.io/myrok_client/storybook/

name: deploy frontend and storybook

on:
  push:
    branches: ['develop']

jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      contents: write

    steps:
      - name: Setup Repository
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 18
          cache: 'npm'
          cache-dependency-path: '**/package-lock.json'

      - name: Setup environment variables
        run: |
          echo "REACT_APP_BASE_URL=${{ secrets.REACT_APP_BASE_URL }}" >> .env
          PUBLIC_URL=$(echo $GITHUB_REPOSITORY | sed -r 's/^.+\/(.+)$/\/\1\//')
          echo PUBLIC_URL=$PUBLIC_URL > .env

      - name: Install dependencies
        run: npm ci

      - name: Build Frontend
        run: npm run build

      - name: Create 404.html for GitHub Pages
        run: |
          cp dist/index.html dist/404.html
          echo '
          <script>
            if (location.pathname.includes("/storybook")) {
              location.href = "/myrok_client/storybook/";
            } 
          </script>
          ' >> dist/404.html

      - name: Upload Frontend to GitHub Pages
        uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./dist
          destination_dir: .
          keep_files: true

      - name: Build Storybook
        run: npm run build-storybook

      - name: Update Storybook Base Path
        run: |
          echo "Updating Storybook base path for /storybook"
          sed -i 's|href="/|href="/storybook/|g' storybook-static/index.html
          sed -i 's|src="/|src="/storybook/|g' storybook-static/index.html

      - name: Upload Storybook to GitHub Pages
        uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./storybook-static
          destination_dir: storybook
          keep_files: true
profile
프론트엔드 개발자 루루

0개의 댓글

Powered by GraphCDN, the GraphQL CDN