⚒️

submoduleで複数ブランドサイトを展開し、ブランドごとに Favicon を差し替える手順

に公開

はじめに

複数ブランド向けに同じサイト基盤(base)を Git Submodule で共有しつつ、ブランド独自の favicon を簡単に差し替える方法をまとめました。
この記事では以下をゴールとします。

  • リポジトリ構成:共通コードとブランド別リポジトリの設計
  • サブモジュール運用フロー:追加・更新・トラブルシューティング
  • Favicon のブランド別上書き:Next.js / Nuxt 3 両対応の汎用パターン
  • 自動デプロイ:GitHub Actions でサブモジュール更新を検知 → 静的ホスティングへ CI/CD

1. リポジトリ構成

└ brand_alpha/                 # ブランド側リポジトリ
   ├ base/                     # 共有コード(submodule)
   ├ .gitmodules
   ├ apps/web/                 # ブランド固有追加コード (任意)
   └ public/
      └ favicon.ico            # ← ブランド専用ファビコン
  • base : すべてのブランドが共通で参照する UI / API / utility 群
  • brand_* : ブランド専用リポジトリ。base/ をサブモジュールとして配置し、必要に応じて独自の UI・設定・画像を上書きします。

Tips:
サブモジュールは「特定のコミットを固定で参照」する仕組みです。base を更新したら、各ブランド側で git submodule update ➜ コミット ➜ PR する必要があります。

2. サブモジュールの追加

# brand_<name> 側で実行
git submodule add https://github.com/your‑org/brand_base.git base
git commit -m "Add base as submodule"

ハマりポイント

エラー 原因 解決策
fatal: ... did not contain <commit> base側の対象コミットがGithubから消えた brand側の.gitmodulesを修正し、git submodule sync
submoduleが古いまま git pull では submodule が更新されない git submodule update --remote --mergeRenovate Bot の導入

3. Favicon をブランドごとに差し替える

3‑1. Next.js の場合

1. base 側の app/layout.tsx(または pages/_document.tsx)で、favicon へのパスを 相対パス で記述します。

<link rel="icon" href="/favicon.ico" sizes="any" />

2. brand 側の public/ に favicon.ico を置く

  • サブモジュール内のファイルは 優先度が低い ため、ルートの public/favicon.ico が勝ちます。
  • PNG / SVG マルチサイズ対応なら <link rel="icon" type="image/png" …> を複数置いて OK。

3‑2. Nuxt 3 の場合

1. base 側の nuxt.config.ts

export default defineNuxtConfig({
  app: {
    head: {
      link: [{ rel: 'icon', href: '/favicon.ico' }],
    },
  },
})

2. brand 側で上書きしたい場合は nuxt.config.ts を extends して favicon だけ変更。

// brand/nuxt.config.ts
import base from './base/nuxt.config'

export default defineNuxtConfig({
  extends: base,
  app: { head: { link: [{ rel: 'icon', href: '/favicon.ico' }] } },
})

ポイント:
サブモジュール配下より上位階層のファイルが優先されるので、ビルド設定はなるべく base に集約し、静的アセットでブランド差異を出すと管理が楽です。

4. GitHub Actions で自動デプロイ

.github/workflows/deploy.yml

name: Deploy Brand Alpha
on:
  push:
    branches: [ main ]
  workflow_dispatch:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          submodules: recursive   # ★ サブモジュールも一緒に取得
      - uses: pnpm/action-setup@v2
        with:
          version: 8
      - run: pnpm install --frozen-lockfile
      - run: pnpm run build
      - name: Upload artifact
        uses: actions/upload-artifact@v4
        with:
          name: dist
          path: .output           # Nuxt3 の例

  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - name: Download artifact
        uses: actions/download-artifact@v4
        with:
          name: dist
          path: ./dist
      - name: Deploy to Hosting
        run: |
          curl -X POST -F "file=@dist.zip" "$WEBHOOK_URL"
  • submodules: recursive を忘れると、CI 上で base ディレクトリが空のままビルド失敗 します。
  • workflow_dispatch を残しておくと、「base 更新→brand 側 submodule 追従コミットなし」でも手動デプロイ可。

5. 運用ベストプラクティス

シーン ベストプラクティス
base 更新を各ブランドへ即時反映したい Renovate などで submodule bump PR を自動生成 → マージで自動デプロイ
ブランド固有のビルド設定が増えてきた base 側の Monorepo(例: pnpm-workspace.yaml)にブランド用パッケージを定義して一元管理
ファビコン以外にも差異がある public/assets/brand/** 配下に静的アセットをまとめ、ブランド側で上書きできる構成にする

まとめ

  • Git Submodule を使えば、共通コードを中央管理しつつブランドサイトを軽量に保守できます。
  • 静的アセット(favicon など)は ブランドリポジトリ 直下で上書きすると衝突せず手軽。
  • GitHub Actions の submodules: recursive を忘れないことが最大の落とし穴。
  • 運用効率を上げるには、自動バージョンアップ Bot・Monorepo 化・ブランチ戦略の整備が鍵です。

Discussion