---
title: "メンバー追加・削除の運用手順"
---

## データの二重管理について

メンバーデータは以下の2か所で管理されている。

| 場所 | 用途 | 更新タイミング |
|------|------|----------------|
| `data/work/members.json` | ビルド時のルート生成（`generateStaticParams`）に使用。各メンバーの詳細ページ URL を静的に生成するためのシード。 | メンバー追加・削除時に手動編集 |
| Vercel Blob (`members.json`) | ランタイムに `getMembersFromBlob()` で読まれる実データ。トップ画面・詳細画面・API が参照する。 | **新規メンバー追加時**: `bun run upload-members` でマージ追加。**既存メンバーの更新**（OG 変更等）: `PUT /api/members` で直接更新 |

**注意**: ランタイムが読むのは Vercel Blob のみ。`data/work/members.json` を編集しただけでは画面に反映されない。Blob も必ず更新すること。

**注意**: `data/work/` は作業用の一時ファイル置き場（`.gitignore` 対象）。`data/work/members.json` は手順のたびに用意し、Blob への反映が完了したら削除してよい。

> **`bun run upload-members` の用途**: 新規メンバー追加専用。Blob-first マージのため、既存メンバーのフィールド変更（OG への変更等）には使用できない。既存メンバーの更新は `PUT /api/members` を使用すること。

---

## 新メンバー追加手順

### 1. `data/work/members.json` を編集する

以下の JSON 構造に従いエントリを追加する。配列末尾に追記することを推奨（id 重複・JSON 構文エラーに注意）。

```json
{
  "id": "yamada-hanako",
  "name": "山田花子",
  "ruby": "やまだ はなこ",
  "status": "Active",
  "generation": 14,
  "profile": {
    "dob": "2005-01-01",
    "joinDate": "2023-04-01",
    "gradDate": null,
    "family": ""
  },
  "latestStatus": {
    "content": "活動状況の初期説明文。",
    "lastUpdated": "2026-01-01",
    "sources": []
  },
  "statusHistory": [],
  "officialSns": [
    {
      "url": "https://example.com/yamada-hanako/",
      "active": true
    }
  ]
}
```

#### フィールド仕様

| フィールド | 型 | 必須 | 説明 |
|-----------|-----|------|------|
| `id` | `string` | ✅ | URLセーフなローマ字表記（例: `yamada-hanako`）。変更不可。 |
| `name` | `string` | ✅ | 表示名（漢字） |
| `ruby` | `string` | ✅ | ふりがな（半角スペース区切り） |
| `status` | `"Active"` \| `"OG"` | ✅ | `Active`=現役、`OG`=卒業済み。**OG は不可逆**（一度 OG にしたら Active に戻さない） |
| `generation` | `number` | ✅ | 期番号（整数） |
| `profile.dob` | `string` | ✅ | 生年月日（`YYYY-MM-DD`） |
| `profile.joinDate` | `string` | ✅ | 加入日（`YYYY-MM-DD`） |
| `profile.gradDate` | `string \| null` | ✅ | 卒業日（`YYYY-MM-DD`）。現役は `null` |
| `profile.family` | `string` | ✅ | 家族構成の説明文（空文字可） |
| `latestStatus.content` | `string` | ✅ | 最新の活動状況テキスト |
| `latestStatus.lastUpdated` | `string` | ✅ | 最終更新日（`YYYY-MM-DD`） |
| `latestStatus.sources` | `string[]` | ✅ | 情報源 URL のリスト（空配列可） |
| `statusHistory` | `StatusEntry[]` | — | 近況履歴。初期値は `[]` |
| `officialSns` | `SnsEntry[]` | — | 公式 SNS URL リスト。省略可。 |
| `officialSns[].url` | `string` | — | SNS の URL |
| `officialSns[].active` | `boolean` | — | `true`=有効（検索対象）、`false`=無効（スキップ） |
| `snsCheck` | `SnsCheck` | — | バッチが自動更新するフィールド。手動設定不要。 |

### 2. Vercel Blob を更新する

`data/work/members.json` の新規メンバーを Vercel Blob の既存データとマージしてアップロードする。

```bash
bun run upload-members
```

実行後に出力される URL が現在の `MEMBERS_BLOB_URL` と一致していることを確認する（固定 URL で上書きされるため通常は同じ）。

> **マージ動作について**:
> `bun run upload-members` は Blob の既存データを保持したままマージを行う（Blob 優先）。
> - `id` をキーに照合し、**Blob にのみ存在するメンバーはそのまま保持**される。
> - **ローカルにのみ存在するメンバー（新規）は追加**される。
> - 同一 `id` のメンバーは **Blob 側の内容が優先**される（バッチや画面編集で蓄積した近況・SNSデータを保護するため）。
>
> **環境変数の準備**:
> `BLOB_READ_WRITE_TOKEN` は Vercel ダッシュボード → Blob ストア → Tokens から取得する。
> ローカルで実行する場合は `.env.local` に設定する。
> `MEMBERS_BLOB_URL` も設定しておくことで Blob の既存データを取得してマージできる（未設定の場合はローカルデータのみアップロード）。

### 3. 再デプロイする

`data/work/members.json` を変更した場合、`generateStaticParams` による静的ルート生成に反映するために再デプロイが必要。

```bash
# Vercel CLI を使う場合
vercel --prod
```

または GitHub に push して Vercel の自動デプロイを待つ。

> **注意**: 再デプロイしないと、新メンバーの詳細ページ（`/members/<id>`）が 404 になる。
> ランタイムのトップ画面は Blob を直接読むため、Blob 更新後はデプロイなしでも反映される。

---

## メンバー削除（または OG への変更）手順

### OG（卒業済み）への変更

> **注意**: `bun run upload-members` は Blob-first マージのため、ローカルで `status: "OG"` に変更しても Blob 側の `status: "Active"` が優先される。**スクリプト経由では OG への変更は Blob に反映されない。** Blob の更新には API への直接呼び出しが必要。

#### 1. `data/work/members.json` を更新する（ビルドシード用）

`status` を `"OG"` に変更し、`profile.gradDate` に卒業日を設定する。

```json
"status": "OG",
"profile": {
  "gradDate": "2026-03-01"
}
```

この変更は静的ルート生成（`generateStaticParams`）にのみ影響する。ランタイムに反映させるには次の手順が必要。

#### 2. Blob を API 経由で更新する

`PUT /api/members` を直接呼び出して Blob を更新する。デプロイ済みのアプリの URL に対して実行する。

```bash
# 1. 現在のメンバーデータを Blob から取得する
curl -s "$MEMBERS_BLOB_URL" -o /tmp/members_current.json

# 2. 対象メンバーの JSON を /tmp/member_patch.json に用意する
#    （/tmp/members_current.json から対象 id のオブジェクトを取り出し、
#     status と profile.gradDate を変更したもの）

# 3. PUT リクエストで Blob を更新する
curl -X PUT https://<your-app>.vercel.app/api/members \
  -H "Content-Type: application/json" \
  -d @/tmp/member_patch.json
```

成功すると `{"message":"Member updated successfully","member":{...}}` が返る。

#### 3. 再デプロイする

`data/work/members.json` を変更した場合は再デプロイして静的ルートに反映させる（[新メンバー追加手順の 3](#3-再デプロイする) と同じ）。

### メンバーエントリの物理削除

原則として物理削除は行わない。過去の活動記録を残すため `status: "OG"` への変更を推奨する。
どうしても削除が必要な場合は、`data/work/members.json` からエントリを削除した後、Blob 更新 → 再デプロイを行う。

---

## officialSns の更新

SNS URL を追加・変更する場合は `data/work/members.json` を編集後、以下のスクリプトで Blob 上のデータに一括マージできる。

```bash
BLOB_READ_WRITE_TOKEN=<token> MEMBERS_BLOB_URL=<url> bun scripts/patch-official-sns.ts
```

詳細は `docs/operations/patch-official-sns.md` を参照。

### Instagram アカウントは公式アカウントのみ登録する

ファン運営の非公式アカウント（`.fanpage` 等）は本人・事務所の管理下になく、非公開化・投稿停止・削除がいつ起きても不思議ではない。登録すると投稿収集（`collect-instagram-posts.ts`）でタイムアウトが連日発生する原因になるため、`officialSns` には公式アカウントのみを登録すること（#1348）。

---

## メンバーカラーの登録・変更

メンバーカラーはメンバー詳細画面の期バッジ、メンバー一覧の期バッジ、歴史タイムラインのカード装飾に使用される。未設定のメンバーは世代共通カラー（`GENERATION_COLORS`）にフォールバックする。

### CSV の管理

カラー定義は `data/inputs/member-colors.csv` で一元管理する。

```
id,color
nakazawa-yuko,#e60012
ishiguro-aya,#ff8c00
```

| 列 | 形式 | 説明 |
|---|---|---|
| `id` | 文字列 | メンバー ID（`data/work/members.json` の `id` と一致させる） |
| `color` | `#RRGGBB` | メンバーカラー（16進数 6 桁） |

### カラーを Blob に反映する

```bash
BLOB_READ_WRITE_TOKEN=<token> MEMBERS_BLOB_URL=<url> bun scripts/patch-member-colors.ts
```

`.env.local` に環境変数が設定済みの場合は以下で実行できる。

```bash
bun --env-file=.env.local scripts/patch-member-colors.ts
```

**動作仕様:**

- CSV に記載された ID だけを対象に Blob の `color` フィールドを上書きする
- CSV に載っていないメンバーは変更されない（既存カラーを保持）
- Blob に存在しない ID はスキップしてログ出力する

### 新メンバー追加時の対応

新メンバーを追加した際は、カラーが決まり次第 CSV に行を追加してスクリプトを実行する。カラーが未設定の間は世代カラーで表示される。

### カラーを変更する場合

CSV の該当行の `color` 値を更新してスクリプトを再実行する。Blob の値が上書きされる。
