0
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?

個人開発2本目のアプリをClaude Codeと作った話 ― 1本目の知見をどう転用したか

0
Posted at

個人開発2本目のアプリをClaude Codeと作った話 ― 1本目の知見をどう転用したか

以前、ビジュアルノベルエンジン「NovelCraft」をClaude Codeと作った話を書いた。今回はその続編として、2本目の個人開発アプリ「StoryForge」(創作者向けの世界観・キャラクター・プロット管理ツール)を作る過程で、1本目の開発から得た知見をどう転用し、何が新たな課題になったかをまとめる。

なぜ2本目を作ったか

NovelCraftの開発・運用を続ける中で、決済基盤やライセンス認証、自動アップデートといった「本番運用に必要な仕組み」は一通り整った。一方で、この仕組みは製品固有のものではなく、個人開発アプリ一般に使い回せる資産のはずだと考えるようになった。ちょうどNovelCraft自体の開発過程で感じていた「設定資料が散らかる」課題があり、それを解決するツールとしてStoryForgeの開発に着手した。

1本目から転用した資産

ゼロから作り直さず、以下をそのまま、または最小限の変更で流用した。

  • ライセンス・決済基盤:Stripe Checkoutとライセンスサーバー(Fly.io上で稼働)をそのまま複数製品対応の構成に拡張し、StoryForge用のライセンスキー発行・検証を追加した
  • 自動アップデートの仕組み:electron-updaterによるチェック・ダウンロード・再起動フロー(ユーザー確認ダイアログを必ず挟む方針含む)を、NovelCraftの実装からほぼそのまま移植した
  • 配布パイプライン:electron-builderによるWindows向けビルド・GitHub Releasesでの配布、公式サイトのダウンロード導線の作り方
  • アイコン生成の手順:ソース画像1枚から.ico/.icns/各サイズPNGを一括生成する手順を確立していたので、StoryForgeでも同じ手順を再利用した

一つ目の製品で「動くようにする」ために試行錯誤した部分が、二つ目では最初から仕組みとして使える状態になっていた。これは製品を作っているというより、個人開発の基盤を作っている感覚に近い。

開発プロセスで変えなかったこと

Claude Codeとの協働の進め方自体は、1本目から変えていない。

  • プロジェクトルールをCLAUDE.mdに明文化し、破壊的操作(ファイル削除等)は必ず承認を得てから実行する、ソースコードは非公開のプライベートリポジトリにバックアップする、公式サイトにはビルド済み成果物のみを置きソースコードは含めない、といった方針を固定する
  • セッションをまたいだ引き継ぎメモは「事実」ではなく「申告」として扱い、実際のソースコードで裏取りしてから信用する
  • 設計判断の「why」(なぜその実装にしたか)は、コードから読み取れない情報としてナレッジベースに書き残し、次回以降はコードを読み直す前にまずそこを参照する

これらは1本目の開発中に「メモを取らないと同じ手戻りを繰り返す」という経験から生まれたルールで、2本目では最初から適用できたため、無駄な調査や再確認が大きく減った。

コードで見る、地味だけど典型的な2つの落とし穴

「基盤を使い回す」と書くと綺麗な話に聞こえるが、実際には1本目で踏んだ落とし穴を2本目でも踏まないように、ということも含んでいる。ここでは2つ紹介する。

1. electron-builderがアイコン未設定に気づかず、黙って既定アイコンにフォールバックする

electron-builderはbuild設定にiconを明示しなくても、buildResources(既定/慣例ではbuildディレクトリ)配下にicon.ico(Windows)・icon.icns(mac)があれば自動的に拾ってくれる。逆に言うと、このファイルが存在しないとエラーにも警告にもならず、Electron側の汎用デフォルトアイコンで静かにビルドが完了する。

// package.json(修正前)
"build": {
  "appId": "com.storyforge.app",
  "directories": {
    "output": "release",
    "buildResources": "build"
  }
  // icon を明示していない、かつ build/icon.ico も存在しない
  // → ビルドは成功するが、.exe・インストーラー・デスクトップショートカットが
  //   すべてElectronの汎用デフォルトアイコンになる
}

BrowserWindowに渡すアプリ内アイコン(new BrowserWindow({ icon: ... }))とは別物なので、開発中にアプリを起動して見た目を確認しているだけでは気づけない。実際に気づいたのは、ビルド済みの.exeをエクスプローラーで見たときだった。

修正は、ソース画像からelectron-icon-builderで各形式のアイコンを一括生成し、buildResourcesが指すディレクトリに置くだけで済む。

npx electron-icon-builder --input=./icon-source.png --output=./build --flatten

修正が効いているかどうかは、ビルドしたexeから実際にアイコンリソースを取り出して目視確認するのが確実だった。

[System.Drawing.Icon]::ExtractAssociatedIcon($exePath).ToBitmap().Save(
  $outPath, [System.Drawing.Imaging.ImageFormat]::Png
)

「設定していないから既定値になる」のではなく「既定値へのフォールバックが完全に無音」である点が、この手のビルド設定系のバグの厄介なところだと感じている。

2. 本番ビルドだけで有効なCSPが、ローディング画面を隠すインラインscriptをブロックしていた

NovelCraftでは開発時のHMR/WebSocketを妨げないよう、CSP(Content-Security-Policy)を本番ビルド限定で有効化している。

// main.ts(本番ビルドのみCSPを付与)
if (process.env.NODE_ENV !== 'development') {
  const csp = [
    "default-src 'self'",
    "script-src 'self'",
    "style-src 'self' 'unsafe-inline'",
    "img-src 'self' data: file: blob:",
    // ...
  ].join('; ');
  session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
    callback({
      responseHeaders: { ...details.responseHeaders, 'Content-Security-Policy': [csp] },
    });
  });
}

script-src 'self'はインラインscriptを許可しない設定である。ところが起動時のローディング画面(public/index.html内の#loading要素)を隠す処理が、当初index.htmlに直書きしたインライン<script>だった。開発時はCSPが無効なので普通に動くが、本番ビルドだけローディング画面が消えずに残り続けるという、環境依存の再現性が低いバグになっていた。

修正の方向性として、CSPを緩めて'unsafe-inline'を許可する選択肢もあったが、それはセキュリティを弱める方向の妥協になる。代わりに、ローディング画面を隠す処理そのものをReact側のエントリーポイント(webpackでバンドルされる外部スクリプト)に移した。

// renderer/index.tsx
// public/index.html側のインラインscriptで行っていたが、本番ビルドのCSP(script-src 'self')で
// インラインスクリプトがブロックされ画面が残り続けるバグがあったため、バンドルされた外部スクリプト側に移動
const loadingScreen = document.getElementById('loading');
if (loadingScreen) {
  setTimeout(() => {
    loadingScreen.style.opacity = '0';
    loadingScreen.style.transition = 'opacity 0.3s ease';
    setTimeout(() => { loadingScreen.style.display = 'none'; }, 300);
  }, 500);
}

バンドルされた<script src="...">はCSPの'self'に該当するため、ブロックされずに動く。「セキュリティ設定を緩めて直す」のではなく「セキュリティ設定に合わせて実装側を直す」という判断を、2本目でも踏襲している。

2本目で新たに直面した課題

一方で、複数プロダクトを並行運用することで初めて出てきた課題もあった。

  • 公式サイトの構成をどう分けるか:1つのポータルサイトの下に各製品のページをぶら下げる構成にしたが、製品ごとのブランディング(ロゴ・配色)と、ポータル全体としての統一感の両立が地味に悩ましい
  • 知見の「製品固有」と「基盤共通」の切り分け:ライセンス基盤のように明確に共通化できるものもあれば、UI設計のようにアプリごとに最適解が変わるものもあり、何でも1本目のコピーで済ませようとすると逆に無理が出る
  • 集客をゼロから作り直す必要があること:決済や配布の「仕組み」は流用できても、ユーザーに知ってもらうための導線(この記事のような発信も含めて)は製品ごとに一から積み上げる必要がある

まとめ

個人開発を1本で終わらせず複数プロダクトに展開する場合、決済・配布・自動アップデートのような「製品を問わない基盤」は積極的に資産化し、2本目以降はそこにかかるコストをほぼゼロにできる。一方で、開発の進め方(ルールの明文化、記録の裏取り、知見の記録)を仕組みとして持っておくことも、基盤のコード以上に効いてくる部分だと感じている。

0
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
0
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?