RORK LABEN
TOOLING — Rorkの開発者向けリポジトリが動き続けています。rork-xcodeが7月16日、rork-deviceが7月15日、rork-plistが7月13日に更新されましたOPUS46 — RorkでClaude Opus 4.6が稼働しています。Rork MaxはClaude Codeを土台にアプリを組み立てる設計ですSIM — ブラウザ上で動くクラウドのiOSシミュレータを備え、実機へのインストールは1クリック、App Storeへの提出は2クリックと案内されていますMAX — Rork MaxはReact Nativeではなく純粋なSwiftを出力します。iPhone・iPad・Apple Watch・Apple TV・Vision Pro、そしてiMessageまでが射程ですNATIVE — HealthKit、ARKitとLiDAR、NFC、Dynamic Island、Live Activities、Metalによる3D、Core MLのオンデバイス推論まで扱えますSEED — RorkはLeft Lane Capitalが主導する1,500万ドルのシードラウンドを実施し、Peak XVとa16z Speedrunが参加しましたTOOLING — Rorkの開発者向けリポジトリが動き続けています。rork-xcodeが7月16日、rork-deviceが7月15日、rork-plistが7月13日に更新されましたOPUS46 — RorkでClaude Opus 4.6が稼働しています。Rork MaxはClaude Codeを土台にアプリを組み立てる設計ですSIM — ブラウザ上で動くクラウドのiOSシミュレータを備え、実機へのインストールは1クリック、App Storeへの提出は2クリックと案内されていますMAX — Rork MaxはReact Nativeではなく純粋なSwiftを出力します。iPhone・iPad・Apple Watch・Apple TV・Vision Pro、そしてiMessageまでが射程ですNATIVE — HealthKit、ARKitとLiDAR、NFC、Dynamic Island、Live Activities、Metalによる3D、Core MLのオンデバイス推論まで扱えますSEED — RorkはLeft Lane Capitalが主導する1,500万ドルのシードラウンドを実施し、Peak XVとa16z Speedrunが参加しました
記事一覧/開発ツール
開発ツール/2026-06-02上級

壁紙アプリ6本を1つのコードベースから出し分ける — app.config.ts と EAS で組むホワイトラベル構成

似た構造の壁紙アプリを別々のリポジトリで保守していると、修正が6箇所に分散して破綻します。1つのコードベースから APP_VARIANT で6本のアプリを出し分けるホワイトラベル構成を、app.config.ts と eas.json の実コードと、設定ドリフトを防ぐ検証スクリプトまで含めてまとめました。

React Native209Expo149EAS6設計8アーキテクチャ18

プレミアム記事

数年前、私の手元には壁紙アプリのリポジトリが6つ並んでいました。テーマも配色も違いますが、骨格はほとんど同じです。グリッド表示があって、お気に入りがあって、AdMob のバナーとインタースティシャルが入っていて、設定画面がある。最初の2本までは「似ているけど別物」として別々に育てていけました。

破綻したのは4本目あたりです。iOS の Privacy Manifest 対応が必要になったとき、私は同じ修正を6つのリポジトリに手で入れていきました。5本目まで終えて満足していたのですが、数週間後、6本目だけが古いままリジェクトされていることに気づきました。コピペの途中で1本飛ばしていたのです。

私は2014年から個人開発を続け、累計5,000万ダウンロードのアプリ事業を1人で回しています。壁紙アプリだけで6本、AdMob からの収益は月100万円規模で、その維持コストは私にとって死活問題です。このとき初めて、「似たアプリを別リポジトリで持つ」こと自体が技術的負債なのだと腹落ちしました。1箇所の修正漏れが、月の収益の数%を一晩で取りこぼすこともあります。本記事は、その6本を1つのコードベースから APP_VARIANT という環境変数1つで出し分けるホワイトラベル構成へ寄せた実装記録です。Expo の app.config.ts を動的関数にする部分から、EAS Build のプロファイル設計、そして「バリアント定義の抜け漏れをビルド前に落とす」検証スクリプトまで、実際に動くコードで示します。

なぜ「共通コンポーネント化」だけでは足りなかったのか

最初に試したのは、共有コンポーネントを npm パッケージや monorepo のワークスペースに切り出す方向でした。これは UI の重複には効きます。けれど、私が本当に困っていたのは UI ではなく**「アプリごとに違う1点もの」の管理**でした。

具体的には、バンドルID(com.dolice.wallpaper.zen のような)、アプリ名、アイコン画像、スプラッシュ画像、AdMob のアプリID と各広告ユニットID、ディープリンクのスキーム、そしてテーマの基調色です。これらは「共通化できない差分」なので、コンポーネントを共有しても残り続けます。monorepo にしても、結局この差分を6つのパッケージそれぞれの app.json に手で書くなら、コピペ漏れのリスクは消えません。

そこで発想を変えました。コードは完全に1つにする。差分はバリアント定義というデータに閉じ込める。ビルド時に APP_VARIANT=zen のように指定すると、app.config.ts がそのバリアントのデータを読んで、その場で正しい app.json 相当を組み立てる。この形なら、Privacy Manifest のような共通修正は1箇所を直すだけで6本に伝播します。差分は1つのファイルに集まっているので、レビューで抜けに気づけます。

バリアントを「型のあるデータ」として定義する

まず、6本分の差分を1つの型付きレジストリにまとめます。ここが設計の心臓部です。as const ではなく明示的な型を付けるのは、後述する検証スクリプトでキーの抜けを検出するためです。

// config/variants.ts
export type VariantId = "zen" | "aurora" | "noir" | "sakura" | "ocean" | "mono";
 
export interface AppVariant {
  id: VariantId;
  name: string;          // ストア表示名・ホーム画面の名前
  slug: string;          // expo の slug(プロジェクト識別)
  scheme: string;        // ディープリンク用 URL スキーム
  bundleId: string;      // iOS / Android 共通の applicationId
  themeColor: string;    // スプラッシュ背景・ステータスバー基調色
  admobAppIdIos: string;
  admobAppIdAndroid: string;
  easProjectId: string;  // EAS のプロジェクトID(バリアントごとに別)
}
 
export const VARIANTS: Record<VariantId, AppVariant> = {
  zen: {
    id: "zen",
    name: "Zen Wallpapers",
    slug: "zen-wallpapers",
    scheme: "dolicezen",
    bundleId: "com.dolice.wallpaper.zen",
    themeColor: "#1B1B1F",
    admobAppIdIos: "ca-app-pub-0000000000000000~1111111111",
    admobAppIdAndroid: "ca-app-pub-0000000000000000~2222222222",
    easProjectId: "00000000-0000-0000-0000-000000000001",
  },
  // aurora / noir / sakura / ocean / mono も同じ形で続けます
} as Record<VariantId, AppVariant>;
 
export function resolveVariant(): AppVariant {
  const id = process.env.APP_VARIANT as VariantId | undefined;
  if (!id) {
    throw new Error(
      "APP_VARIANT が未設定です。例: APP_VARIANT=zen npx expo prebuild"
    );
  }
  const variant = VARIANTS[id];
  if (!variant) {
    const known = Object.keys(VARIANTS).join(", ");
    throw new Error(`未知の APP_VARIANT: "${id}"。有効な値: ${known}`);
  }
  return variant;
}

ここで大事なのは、resolveVariant()未設定や typo を黙って通さないことです。APP_VARIANT を付け忘れたまま eas build を回すと、デフォルト値で6本目のアプリが「Zen の設定で Ocean としてビルドされる」ような事故が起きます。私は一度これで、別アプリのバンドルIDでアップロードしかけました。例外で即座に止めるだけで、この種の事故はほぼ消えます。

ここまでお読みいただきありがとうございます。

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
app.config.ts を動的関数にして APP_VARIANT 1つでバンドルID・アイコン・スプラッシュ・AdMob ID・テーマ色を切り替える実コード(型付きバリアントレジストリ付き)
6本を別リポジトリで運用していた頃に同じ修正を6回コピペして1本だけ古いまま放置した私の失敗と、単一コードベースへ寄せた移行の判断基準
バリアント定義の抜け漏れ(AdMob ID 未設定・アイコン欠番)をビルド前に落とす TypeScript の検証スクリプトと、CI に組み込む運用ルール
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

お読みいただきありがとうございます

Rork Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

関連記事

開発ツール2026-07-14
AI再生成に壊されない設計 — Rork の生成コードと手書きロジックを分ける境界の引き方
Rork へ追加指示を出すたびに手書きのロジックが消える。プロンプトで守るのは対症療法です。生成コードと自分の所有コードを層で分け、再生成が届かない境界を設計で引く方法を、Zustand と service 層の実装例つきでお伝えします。
開発ツール2026-07-08
端末の空き容量が尽きたとき、アプリは何を守るか
書き込み失敗を前提に設計するExpo/React Nativeアプリの耐性パターン。空き容量予算・LRU退避・データ保護優先度・無言失敗を防ぐテレメトリまで、6本の壁紙アプリの運用から得た実装をまとめました。
開発ツール2026-06-22
壊れたキャッシュで毎回起動時に落ちるアプリ——ユーザーが自力で抜け出せる「セーフモード起動」を設計する
永続化したキャッシュが壊れて起動のたびに同じ場所で落ちると、ユーザーには再インストールしか残りません。アプリ自身が早期クラッシュの連続を数え、対話可能になって初めて起動を確定し、危険な状態だけを段階的にリセットするセーフモードを、Expo(React Native)の実装で設計します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →