1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

モノレポアプリケーションをGitHubActions+StaticWepAppsによるCICD構想のお話

Last updated at Posted at 2025-04-02

はじめに

本記事はSWAを通じ、モノレポ構成に準じたGitHubActionでのCICD実装を行います。
初めて触るGithubActionコードと、モノレポ構成独特のCICD構成を考えてみましたので、よければご参考ください。では〜!

環境設定

環境・アプリケーション構成

  • GitHub: ソースコード管理とCI/CDパイプラインの実行環境
  • Azure: デプロイ先のクラウドプラットフォーム(Azure Static Web Apps)
  • モノレポアプリケーション: Turborepoなどで管理されたモノレポ構成の複数フロントエンドアプリケーション

今回、すでにTurborepoを用いたモノレポアプリケーションを前提に、GitHubActionを記載していきます。
フロントエンドの環境構築についての記事もあるのでぜひ〜!
React+Vite+Turborepo+TanstackRouterを用いた爆速フロントエンド開発のお話

Static Web Apps

概要

Azure Static Web Apps(以降略、SWA)とは、Azure上でWebアプリケーションを簡単にデプロイするサービスです。
主な特徴として、

  • 静的コンテンツのWebホスティング:インターネット上でWebサイトを公開・アクセス可能にします
  • GitHub統合による自動ビルドとデプロイ:コード変更からデプロイまでを一連の流れで自動化します
  • プレビュー環境の自動作成:プルリクエストごとに独立した環境が自動的に構築されます
  • 様々なフレームワーク対応:React、Vue、Angularなどの主要JavaScriptフレームワークから、Gatsby、VuePressなどの静的サイトジェネレーターまで幅広くサポートしています
  • グローバルホスティング:CDNを活用し、ユーザーに近い場所からコンテンツを配信することで高速なアクセスを実現します

などが挙げられます。

SWAの作成手順

今回作成するアプリケーションのデプロイ先としてSWAを利用します。
作成方法は様々ですが、今回はAzure Portal上より作成する一例をご紹介します。

  1. Azure Portalにアクセスし、Static Web Appsを作成します。

  2. GitHubリポジトリからソースを取得します。

    GitHubアカウントと連携し、デプロイするアプリケーションのあるレポジトリと接続します。
    デプロイには数分かかる場合があります。デプロイ後、任意のURLが発行されます。発行されたURLにアクセスして動作確認を行います。

SWAは、Free、Standard、Dedicated (プレビュー) の3プランから選択できます。
それぞれアプリサイズや制限などが異なります。詳細はこちら→Azure Static Web Apps ホスティング プラン

初期GitHubActionファイル

SWAはGitHubとの連携が完了すると、GitHubActionを制御するためのyamlファイルが自動的に生成されます。作成されたファイルは自動的にコミットし、GitHub管理されます。
また必要なSecrets値は自動的に作成、保管されます。
ファイル格納先は以下のような構成です。

.github
 └workflows
    └hoge.yml

ここで作成されたワークフローのYAMLファイル名でのみ、本番環境へビルドされます。ファイル名を変更すると正しくデプロイされないのでご注意を!!

こちらの内容をもとにGitHubActionの構成について説明していきます。

初期作成yaml 全量
name: Azure Static Web Apps CI/CD

on:
  push:
    branches:
      - main
  pull_request:
    types: [opened, synchronize, reopened, closed]
    branches:
      - main

jobs:
  build_and_deploy_job:
    if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed')
    runs-on: ubuntu-latest
    name: Build and Deploy Job
    steps:
      - uses: actions/checkout@v3
        with:
          submodules: true
          lfs: false
      - name: Build And Deploy
        id: builddeploy
        uses: Azure/static-web-apps-deploy@v1
        with:
          azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_HOGE }}
          repo_token: ${{ secrets.GITHUB_TOKEN }}
          action: "upload"
          app_location: "./apps"
          api_location: ""
          output_location: "build"

  close_pull_request_job:
    if: github.event_name == 'pull_request' && github.event.action == 'closed'
    runs-on: ubuntu-latest
    name: Close Pull Request Job
    steps:
      - name: Close Pull Request
        id: closepullrequest
        uses: Azure/static-web-apps-deploy@v1
        with:
          azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_HOGE }}
          action: "close"

GitHub Actions設定の詳細

1. ワークフロー名

name: Azure Static Web Apps CI/CD #ワークフロー名

GitHubのAction名として表示されます。
GitHub上ではこのように表示されます。

2. トリガー設定

on:
  push: # pushをトリガーとする
    branches:
      - main # メインブランチ
  pull_request: # PR作成をトリガーとする
    types: [
      opened,      # 新規PR作成
      synchronize, # PR更新(新しいコミットが追加された時など)
      reopened,    # PR再オープン
      closed       # PRクローズ
    ]
    branches:
      - main # メインブランチ

ワークフローを実行するトリガーを定義します。
この例では「メインブランチへのPush」または「メインブランチへのPRを作成、更新、再オープン、クローズ時」に実行されるワークフローとなります。

GitHubActionでは様々なトリガーが設定可能です。またPRトリガーの中のアクテビティもたくさんあるので、以下公式サイトをご確認ください。

3. ジョブ設定

jobs:
  build_and_deploy_job: # 任意のジョブID
    if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed') # 実行条件
    runs-on: ubuntu-latest # 実行環境
    name: Build and Deploy Job # 任意のジョブ名
    steps:
      〜略〜

ワークフローの処理を定義します。
ここでは任意のジョブIDを指定して、そのIDに紐づくジョブを作成します。この例ではbuild_and_deploy_jobというIDを使用しており、このIDはのちのジョブの参照や依存関係の設定に利用できます。

その下には条件や、実行環境、ジョブ名などが記載されます。
ここでは、条件として「Pushイベントが発生した場合」または「PRイベントが発生し、かつそのアクションがクローズでない場合」という条件を設定しています。この条件により、PRがクローズされた時にはこのジョブは実行されません。

また、実行環境としてはUbuntu Linuxの最新バージョンを使用するよう指定しています。

nameは、ジョブIDとは異なり、ジョブ名として利用されるため、認識しやすい名称を付与します。
GitHub上ではこのように表示されます。

ジョブは複数作成することができ、デフォルトでは並列で実行されます。しかし完了したら実施する、など依存関係を持つジョブを作成することも可能です。

jobs:
  test: # 任意のジョブIDA
    if: github.event_name == 'pull_request' && github.event.action != 'closed' # 実行条件

  build: # 任意のジョブIDB
    needs: test # 依存関係ジョブID(ここでは上記ジョブID)
    if: ${{ !cancelled() && !failure() }}  # 実行条件

上記の例では、testジョブに依存しbuildジョブが設定されており、testジョブ→buildジョブの順で実行されます。

GitHub上では以下のように表示され、ジョブ関係が線で繋がって表示されます。
image.png

またifに実行条件が記載されており、この例でのbuildジョブの条件は、testジョブに成功した場合、またはtestジョブがスキップされた場合で実行されます。

条件についてわかりやすかった記事はこちら↓

4. ステップ設定

ジョブ内には、ステップという一連のタスクが連なっています。

jobs:
  build_and_deploy_job:
    〜略〜
    steps:
      - uses: actions/checkout@v3 #Github提供の固定アクション
        with:
          submodules: true # サブモジュールを含める
          lfs: false #Git Large File Storageファイルをダウンロードしない
      - name: Build And Deploy # 任意のステップ名
        id: builddeploy # 任意のステップID
        uses: Azure/static-web-apps-deploy@v1 #Github提供の固定アクション(SWA専用)
        with:
          〜略〜

ジョブ内の具体的な処理内容をステップとして定義します。
各ステップは-で始まる行で表され、それぞれが独立した処理単位となります。
ここでは条件や、実行コマンド、ステップ名などが記載されます。

特にusesには様々な便利なアクションが提供されています。@の後にはバージョン番号を指定できます。
以下は代表的なアクションの例です:

  • actions/checkout: リポジトリのコードをチェックアウトするアクション

  • actions/setup-node: Node.jsの環境をセットアップするアクション

  • pnpm/action-setup: pnpmパッケージマネージャーをセットアップするアクション

GitHub上ではこのように表示されます。

SWA専用Action

with:
  azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_HOGE }} # SWAトークン
  repo_token: ${{ secrets.GITHUB_TOKEN }} # GitHubトークン
  action: "upload" # アップロード
  app_location: "./apps" # フロントエンドアプリケーションのソースコードパス
  api_location: "" # APIアプリケーションのソースコードパス
  output_location: "build" # ビルドファイル生成パス

SWAの大きな特徴の一つは、ビルドとデプロイのプロセスが統合されていることです。上記の設定では、アプリケーションのソースコード場所(app_location)、APIの場所(api_location)、ビルド出力先(output_location)などを指定することで、GitHub Actionsが自動的にアプリケーションをビルドし、そのままAzureへデプロイします。

その他の詳細な設定オプションについては、以下公式サイトをご確認ください。

また、もう一つのSWAの特徴である、PR作成時のプレビュー環境についても上記 build_and_deploy_jobジョブ内で実施されます。
mainブランチへのデプロイの場合は本番デプロイ、それ以外のデプロイの場合は一時的なプレビュー環境へのデプロイがされます。
このプレビュー環境はPRがクローズ時と一緒にクローズするため、以下の様なアクションが必要となります

close_pull_request_job:
    if: github.event_name == 'pull_request' && github.event.action == 'closed' # 実行条件
    runs-on: ubuntu-latest # 実行環境
    name: Close Pull Request Job # 任意のジョブ名
    steps:
      - name: Close Pull Request # 任意のステップ名
        id: closepullrequest # 任意のステップID
        uses: Azure/static-web-apps-deploy@v1 #Github提供の固定アクション(SWA専用)
        with:
          azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_HOGE }} # SWAトークン
          action: "close" # クローズ

プレビュー環境の上限はSWAのプランによって異なります。
Freeプランの場合は3環境、Standardプランの場合は10環境、Dedicatedプランの場合は20環境が上限となっています。

モノレポ環境でのCICD実装

モノレポ環境でのCICD実装には複数のアプローチが考えられます。ここでは、私が検討した2つの方法について説明します。

1. パスベースの分岐方式

トリガー箇所にターゲットとなるパスを指定し、1つのアプリケーションを監視するように指定します。

on:
  push: # pushをトリガーとする
    branches: 
      - main # メインブランチ
    paths:
      - apps/management/** # Managementアプリケーションのファイル
      - packages/** # 共通UIパッケージのファイル
      - .github/workflows/azure-static-web-apps-hoge.yml # ワークフローのファイル
      - package.json # パッケージマネージャーのファイル
      - turbo.json # ビルドツールのファイル

このように指定したフォルダ配下やファイルにのみ加えられた変更がトリガーとなり、実行されます。

全量例
name: App CI/CD

on:
  push: # プッシュ時の処理
    branches:
      - main
    paths:
      - apps/management/** # Managementアプリケーションのファイル
      - packages/** # 共通UIパッケージのファイル
      - .github/workflows/azure-static-web-apps-hoge.yml # ワークフローのファイル
      - package.json # パッケージマネージャーのファイル
      - turbo.json # ビルドツールのファイル
  pull_request: # PR時の処理
    types: [
      opened,      # 新規PR作成
      synchronize, # PR更新
      reopened,    # PR再オープン
      closed       # PRクローズ
    ]
    branches:
      - main
      - develop
    paths:
      - apps/management/** # Managementアプリケーションのファイル
      - packages/** # 共通UIパッケージのファイル
      - .github/workflows/azure-static-web-apps-management-kind-mud-049051900.yml # ワークフローのファイル
      - package.json # パッケージマネージャーのファイル
      - turbo.json # ビルドツールのファイル

jobs:
# フロントエンドアプリケーションのテスト
  test-management:
    if: github.event_name == 'pull_request' && github.event.action != 'closed'
    runs-on: ubuntu-latest
    name: Test Management
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: Run Test Management
        run: |
          # テスト作成時に記述する example(pnpm --filter management test)
          echo "Management App Tested"

  # フロントエンドアプリケーションのビルド
  build-management:
    needs: test-management
    if: ${{ !cancelled() && !failure() && (github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed')) }}
    runs-on: ubuntu-latest
    name: Build Management
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          submodules: true
          lfs: false

      - name: Build Management
        run: |
          npm install -g pnpm@10.0.0
          pnpm install
          pnpm --filter management build
          echo "Management App Built"

      - name: Upload Management Build Artifact
        uses: actions/upload-artifact@v4
        with:
          name: management-build
          path: apps/management/dist
          retention-days: 1 # ビルド成果物の保存期間(日)

  # フロントエンドアプリケーションのデプロイ
  deploy-management:
    needs: build-management
    if: ${{ !cancelled() && !failure() && (github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed')) }}
    runs-on: ubuntu-latest
    name: Deploy Management
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          submodules: true
          lfs: false

      - name: Download Management Build Artifact
        uses: actions/download-artifact@v4
        with:
          name: management-build
          path: apps/management/dist
          
      - name: Deploy Management
        id: deploymanagement
        uses: Azure/static-web-apps-deploy@v1
        with:
          azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_HOGE }}
          repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments)
          action: "upload" # デプロイアクション
          app_location: "./apps/management/dist" # アプリケーションの場所
          output_location: "" # ビルド出力先
          skip_app_build: true # ビルドをスキップ
          
  # PRクローズ時の処理
  # Azure Static Web Appsのプレビュー機能を使用するため、PRクローズ時の処理を記述
  close_pull_request_job_management:
    if: github.event_name == 'pull_request' && github.event.action == 'closed'
    runs-on: ubuntu-latest
    name: Close Management App PR Environment
    steps:
      - name: Close Pull Request (Management)
        id: closepullrequestmanagement
        uses: Azure/static-web-apps-deploy@v1
        with:
          azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_HOGE }}
          action: "close"

2. Git差分フラグ分岐方式

モノレポ構成では、Turborepoを利用することで効率的なビルド管理が可能です。Turborepoが提供するFilter機能を活用し、変更があったパッケージのみをビルド対象とするフラグ管理を実装します。

- name: Check App Changed
  id: check-changes
  run: |
    # Git履歴に差分があるか確認
    GIT_CHANGES=$(git diff --name-only HEAD^1 HEAD || echo "")

    # アプリケーションごとに変更を検出
    FRONTEND_CHANGED=$(pnpm turbo run build --dry-run --filter="frontend[HEAD^1]" | grep frontend || echo "")
    MANAGEMENT_CHANGED=$(pnpm turbo run build --dry-run --filter="management[HEAD^1]" | grep management || echo "")          
    
    # FrontendとManagementに変更がなく、Git履歴に差分があれば、共通リソースへの変更として扱う
    if [ -z "$FRONTEND_CHANGED" ] && [ -z "$MANAGEMENT_CHANGED" ] && [ -n "$GIT_CHANGES" ]; then
      echo "frontend-changed=true" >> "$GITHUB_OUTPUT"
      echo "management-changed=true" >> "$GITHUB_OUTPUT"
    else
      echo "frontend-changed=$([ -n "$FRONTEND_CHANGED" ] && echo "true" || echo "false")" >> "$GITHUB_OUTPUT"
      echo "management-changed=$([ -n "$MANAGEMENT_CHANGED" ] && echo "true" || echo "false")" >> "$GITHUB_OUTPUT"
    fi

TurborepoコマンドおよびGitコマンドを用いて、コードベースの差分を検出します。
Turborepoのフィルター機能(--filterオプション)を使用することで、変更があったパッケージのみを特定できます。

この例では差分検出のみを行うジョブを作成し、その結果を出力変数(outputs)として後続のジョブに渡します。後続のジョブではこの変数を条件として使用し、必要なジョブのみを実行する仕組みになっています。

全量例
# アプリケーションの変更を検知
  detect-changes:
    runs-on: ubuntu-latest # 実行環境
    outputs: # 変数
      frontend-changed: ${{ steps.check-changes.outputs.frontend-changed }} 
      management-changed: ${{ steps.check-changes.outputs.management-changed }}
    name: Detect Changes # 任意のジョブ名
    steps:
      - name: Checkout # 任意のステップ名
        uses: actions/checkout@v4 #Github提供の固定アクション
        with:
          # ブランチの履歴を全て取得
          fetch-depth: 0

      - name: Setup Node # 任意のステップ名
        uses: actions/setup-node@v4 #Github提供の固定アクション
        with:
          # package.jsonのenginesに記載されているバージョンを使用
          node-version: '18'

      - name: Setup pnpm # 任意のステップ名
        uses: pnpm/action-setup@v4 #Github提供の固定アクション
        with:
          # package.jsonのpackageManagerに記載されているバージョンを自動使用
          # pnpm installを実行
          run_install: true

      - name: Check App Changed # 任意のステップ名
        id: check-changes # 任意のステップID
        run: | #実行コマンド
          # Git履歴に差分があるか確認
          GIT_CHANGES=$(git diff --name-only HEAD^1 HEAD || echo "")

          # アプリケーションごとに変更を検出
          FRONTEND_CHANGED=$(pnpm turbo run build --dry-run --filter="frontend[HEAD^1]" | grep frontend || echo "")
          MANAGEMENT_CHANGED=$(pnpm turbo run build --dry-run --filter="management[HEAD^1]" | grep management || echo "")          
          
          # FrontendとManagementに変更がなく、Git履歴に差分があれば、共通リソースへの変更として扱う
          if [ -z "$FRONTEND_CHANGED" ] && [ -z "$MANAGEMENT_CHANGED" ] && [ -n "$GIT_CHANGES" ]; then
            echo "frontend-changed=true" >> "$GITHUB_OUTPUT"
            echo "management-changed=true" >> "$GITHUB_OUTPUT"
          else
            echo "frontend-changed=$([ -n "$FRONTEND_CHANGED" ] && echo "true" || echo "false")" >> "$GITHUB_OUTPUT"
            echo "management-changed=$([ -n "$MANAGEMENT_CHANGED" ] && echo "true" || echo "false")" >> "$GITHUB_OUTPUT"
          fi

  test-build-deploy-frontend:
    needs: detect-changes
    if: ${{needs.detect-changes.outputs.frontend-changed == 'true' && (github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed')) }} # 実行条件
    runs-on: ubuntu-latest # 実行環境
    name: Frontend # 任意のジョブ名
    steps:
      - name: Checkout # 任意のステップ名
        uses: actions/checkout@v4 #Github提供の固定アクション

      - name: Run Test Frontend # 任意のステップ名
        run: | #実行コマンド
          # テスト(仮)
          echo "Frontend App Tested"

      - name: Build Frontend # 任意のステップ名
        run: | #実行コマンド
          # ビルド(仮)
          echo "Frontend App Built"

GitHubActionのデバッグログを出力することも可能です。
ランナーログ(ACTIONS_RUNNER_DEBUG)とステップログ(ACTIONS_STEP_DEBUG)の2種類があります。
ランナーログはワークフロー全体の詳細情報を、ステップログは各ステップの実行過程で[debug]というプレフィックス付きの詳細情報を表示します。
これらを有効にするには、GitHubのリポジトリ設定でSecret値として登録し、値をtrueに設定するだけです。

ログはGithub上または、ログをダウンロードすることで確認できます。

詳細はこちら→デバッグ ログを有効にする

SWAコンフィグファイル

SWAの構成をstaticwebapp.config.jsonファイルにて定義を行います。

実際の具体的エラーに基づきながら説明します。
シングルページアプリケーション(SPA)構成の場合、直パスやリロード時パス通りアクセスした場合存在しないhtmlへアクセスしにいくため404エラーとなります。

この原因を深掘りしていくために新ためてSPAについて理解を深めていくとします。
シングルページアプリケーション(SPA)は文字どおり1つのhtmlをベースとし、変動するコンテンツ情報を取得しクライアント側でhtmlを生成、描写をします。

具体的なコードを用いると、index.htmlrootIDのあるdivタグに描写する情報をmain.tsxで定義しています。

index.html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + React + TS</title>
  </head>
  <body>
    <div id="root"></div> # ここに変動的にコンテンツが描写される
    <script type="module" src="/src/main.tsx"></script> # Reactアプリケーションを初期化・実行するJavaScriptを読み込む
  </body>
</html>
main.tsx
const rootElement = document.getElementById("root")!;
if (!rootElement.innerHTML) {
  const root = ReactDOM.createRoot(rootElement);
  root.render(
    <StrictMode>
        <RouterProvider router={router} />
    </StrictMode>,
  );
}

従来のマルチページアプリケーション(MPA)の場合、画面ごとにサーバーで作成されたhtmlを呼び出しますが、
シングルページアプリケーション(SPA)の場合、htmlは1つしかないため、存在しない画面のhtmlを呼び出す形となり404エラー、となるのです。

これを解消するために、SWA設定にてリソースがないリクエストの場合に指定のhtmlを返すよう設定します。
シングルページアプリケーション(SPA)の場合はindex.htmlとなります。

{
  "navigationFallback": {
    "rewrite": "/index.html"
  }
}

作成されたstaticwebapp.config.json/publicに格納し、ビルド時にはビルドファイルへコピーされます。
デプロイの際、GithubActionのyamlファイル上で定義しているapp_locationの位置にstaticwebapp.config.jsonが配置されるようにしてください。

便利ツール

最後に!GitHubActionを作成に当たり、便利ツールをご紹介!

YAMLチェックツール

GitHub Actionsのワークフローファイル(YAML)の構文チェックができるツールです。
https://rhysd.github.io/actionlint/

ローカル実行環境 - act

GitHub Actionsをローカル環境で実行・テストできるツールです。
https://qiita.com/kaminuma/items/37838dd31aca91944453

  • 個人環境ではRancher Desktopを利用しています
  • Docker CLIが有効になっている必要があります

まとめ

今回はモノレポ構成アプリケーションへのGithubAction+Azure StaticWebAppsを用いたCICD構成を考え実装してみました!
はじめて触るGithubActionだったり、SWA独自の設定方法などで悩む部分が多くありましたが、とても勉強になりました。またまだキャッシュやデプロイ構成など改良余地ありだと思うのでブラッシュアップしていければと思います。
引き続き頑張っていきますのでよろしくお願いします〜!

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?