上原正吉(EarthLink Network Co., Ltd.)。Claude Codeを開発の主体に据え、20を超えるプロダクトを1人で同時に開発・運用しています。これは、その現場の実測記です。

2026 年 6 月、私の手元には共通ロジックを抱えたリポジトリが 5 つ、バラバラに存在していました。ユーティリティ集、DynamoDB のヘルパー、レートリミッタ、ブロックエディタ機構、そして通知。それぞれ別のタイミングで生まれ、別のプロダクトから参照され、依存の向きも publish 先も揃っていない。この記事では、それを 1 つの monorepo に集約し、「共通基盤(provider)とプロダクト(consumer)」の境界をどう引き直したか、そこで一番効いた設計判断は何だったかを書きます。
先に結論を置きます(本文の読了は約 7 分)。
process.env を読まない — テーブル名などの環境依存の値は consumer から注入させます。これが一番効いた判断で、パッケージがどの環境にも移植できます。集約前の状態を棚卸しすると、こうなっていました。
v1.0.2): GitHub Packages に publish 済みの DynamoDB ヘルパー。block-cms や block-editor などのパッケージ群。2 つのプロダクトが同時に参照する「唯一のソース」。問題は「共通ロジックが 5 か所に散り、各プロダクトがそれぞれ違うやり方で参照している」ことでした。あるものは公開 npm、あるものは GitHub Packages、あるものはローカルにコピー。更新のたびに「どれが正か」を探す羽目になります。
整理にあたって 3 つの道を考えました。
@org/* として GitHub Packages に publish する — CI とバージョニングを 1 か所に寄せられます。選んだのは B です。6 月 25 日に共通パッケージ monorepo を scaffold し、packages/ 配下にユーティリティ・DynamoDB 共通・共有ライブラリ・ブロックエディタ・通知を集めました。README に置いた一文が、この基盤の性格を決めています。
ランタイム(API / 管理画面 / データ)は持たない。それらはプロダクト側に同居させる。
つまりこの monorepo は provider(ライブラリの供給者)に徹し、runtime を持たない。API も画面もデータアクセスの実体も、consumer 側のプロダクトが持つ。集約するのは「使い回すコードだけ」と線を引いたわけです。

process.env を読まない集約そのものより効いた判断は、共通基盤(provider)と利用側(consumer)の境界をどこに引くかでした。ブロックエディタの README にその原則が明文化されています。
| @org/block-cms | ブロック CMS(型・repository・dynamodb-client・
seeds・theme-presets。テーブル名は consumer から注入。
パッケージ内で process.env を読まない) |
共通パッケージの中で process.env を読まない。DynamoDB のテーブル名のような環境依存の値は、パッケージが自分で環境変数から拾うのではなく、consumer(プロダクト)から引数・設定オブジェクトとして注入させる。これが、この設計の中心です。
なぜこれが重要か。もし共通パッケージが process.env.DYNAMODB_TABLE を内部で読んでしまうと、そのパッケージは「特定の環境変数名」という暗黙の契約を consumer に強制します。プロダクト A は DYNAMODB_TABLE、プロダクト B は TABLE_NAME を使っていたら、もう共通化できません。さらに、Amplify SSR のように実行時に env が伝搬しづらい環境もあり、「ライブラリが env をいつ読むか」が consumer 側のデプロイ形態に依存してしまう。env を読む責務を consumer に一元化することで、共通パッケージは純粋な関数群でいられ、どの環境にも移植できる。テストも、env をモックせず引数を渡すだけで書けます。
consumer 側は GitHub Packages を向く .npmrc を置いて取得します。
# consumer 側 .npmrc
@org:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${NPM_TOKEN}
そして provider 側のビルド・publish は monorepo にまとまるので、pnpm -r build で全パッケージを一括ビルドできます。React UI も純ロジックも同居しますが、pnpm workspaces がパッケージごとに依存と tsconfig を分離するので、UI パッケージの依存が純ロジック側に漏れることはありません。

集約の山場は、プロダクト側をローカル実装から共通パッケージ参照へ切り替える移行でした。6 月上旬、2 つのプロダクトの LP 機能を共通パッケージ v1.0.0 に移行したフェーズでは、片方で約 60 ファイルが変更になり、もう片方ではローカルに抱えていたエディタ実装 15 ファイルを削除しました。「共通化」は足し算ではなく、consumer 側の重複コードを消す引き算として現れます。この引き算が効いた瞬間に、初めて「1 か所」の恩恵が出ます。

もう 1 つ、通知サービスの引っ越しは「責務の移管」でした。もともと独立サービスだったものを共通基盤の packages/ に取り込み、送信ロジックとクライアントを分けて(notify / notify-client)、プロダクトからは薄いクライアント越しに使う形にしました。provider に寄せるとき、何を provider に置き何を consumer に残すかは、通知の場合「送る仕組みは共通、送る内容や宛先はプロダクト固有」で切りました。
この集約が効くのは「複数の consumer が同じロジックを参照する」ときだけです。consumer が 1 つしかないなら、monorepo 集約のオーバーヘッド(publish・バージョニング・CI)は割に合わず、プロダクトに同居させたほうが速い。逆に 2 つ以上が同じコードのローカルコピーを持ち始めたら、それが集約のサインです。私の場合、ブロックエディタを 2 プロダクトが参照し始めたのが転換点でした。
process.env を読まない。環境依存の値は consumer から注入させる。これで env を読む責務が一元化され、パッケージがどの環境にも移植できる。上原正吉。EarthLink Network Co., Ltd. でAI開発をしています。2025年からClaude Codeを開発の主体に据え、今は20を超えるプロダクトを1人で同時に開発・運用しています。この連載では、その現場で実際に起きたこと(うまくいったことも、失敗も)を、数字と一緒に書いていきます。
また、AIで業務や開発を組み替えたい会社・チーム向けに、AI活用のコンサルティングも受け付けています。ご相談は www.eln.ne.jp からどうぞ。
EarthLink Network は、会社の全業務を AI で回すために、必要になったものを自社で作っています。いま作っているプロダクトの一覧と概要は、こちらにまとめています。
→ EarthLink Network が自社でつくっている18のプロダクト
会社と各プロダクトの詳細は、公式サイト www.eln.ne.jp をご覧ください。