フロントエンド開発環境の基礎6つを2026年基準で整理し直す

 ・ 13分

photo by Agnese Rudzīte(https://unsplash.com/@agneserudzite?utm_source=templater_proxy&utm_medium=referral) on Unsplash

2024年に、あるフィンテック企業のフロントエンドプラットフォームチームの採用に向けて勉強したノートが残っていました。サービスを作る開発者ではなく、その開発者たちが使う開発環境やフレームワークを作るポジションです。2年ぶりに開いてみると、採用は当然終わっていて、そこに書かれていた技術もかなり変わっていました。

ただ、募集要項にあった事前課題の出題範囲6行は、今見ても良いチェックリストでした。Git、モジュールシステム、package.jsonの依存関係、バンドラー、CI/CD、エラー検知。フロントエンドの開発環境が裏側でどう動いているかを、この6つでほぼカバーできるんです。そこでこの6行を骨組みにして、中身を2026年基準で埋め直してみました。

  1. Gitを構成する要素と、よく使うコマンドの動作
  2. Node.jsにおけるCommonJSとECMAScript Modulesの使い方と相互互換性
  3. package.jsonのdependencies、devDependencies、peerDependenciesの動作
  4. トランスパイラーとバンドラーの動作と活用
  5. フロントエンド開発環境でのCI/CD構成
  6. フロントエンドサービスとSSRサーバーを安全に運用するためのエラー検知とデバッグ

面接向けのフロントエンド概念全般はフロントエンド応募前に整理しておきたい概念に別途まとめてあります。この記事はその一段下、開発環境側の話です。

1. Git:大きなリポジトリを丸ごと取得しない#

Gitは、ファイル内容(blob)、ディレクトリ構造(tree)、スナップショット(commit)というオブジェクトを積み上げる保存庫です。リポジトリが小さいうちはこの構造を知らなくても困りませんが、数百のサービスをひとつのモノレポに入れると話が変わります。私が見た講演では、リポジトリが40GBまで育って、そのままクローンすること自体が不可能になったそうです。

最初に思いつくのはshallow clone(--depth)です。履歴を切り詰めて取得するので速いのですが、変更履歴を追いにくくなり、変更されたサービスだけを選んでデプロイする用途には向きません。そこで使うのが、Git 2.22から入ったpartial cloneです。履歴は全部取得しつつ、ファイル内容(blob)は必要になったときに取りに行きます。

# コミットとツリーは全部取得し、ファイル内容(blob)は必要なときに取得します
git clone --filter=blob:none --sparse <repo>
# 作業ツリーに載せるディレクトリだけ選ぶと、その中のblobだけダウンロードされます
git sparse-checkout set apps/web packages/ui

2026年時点では、partial cloneとsparse-checkoutを組み合わせるのが大きなモノレポの標準的な構成です。sparse-checkoutは作業ツリーにどのパスを載せるかを決める機能で、ディレクトリ単位でしか指定しないconeモードがGit 2.37からsetのデフォルトになっています。2つの役割は別です。partial cloneはダウンロードを後回しにし、sparse-checkoutはディスクに載せる範囲を減らします。

CIランナーではもう一歩進めます。毎回のチェックアウトでタイムアウトするなら、リポジトリをあらかじめ複製したコンテナイメージを毎日焼いておき、その上で差分だけ取得する方法があります。私が見た事例では、これで36分かかっていたチェックアウトが22秒になりました。GitHub Actionsを使っているなら、actions/checkoutのfilterオプションでpartial cloneをそのまま使えます。

ブランチごとにディレクトリを分けたいならgit worktreeも覚えておいてください。ひとつのリポジトリで複数のブランチを同時にチェックアウトしておける機能で、複数のエージェントが同じリポジトリで同時に作業するときに特に便利です。

2. CommonJSとESM:require(esm)が使えるようになりました#

2024年のノートで唯一空欄だった項目です。そしてその間に最も変わった項目でもあります。

CommonJS(CJS)はrequire()とmodule.exportsを使うNode.js本来のモジュールシステムで、ファイルを同期的に読み込んで実行します。ESM(ECMAScript Modules)はimportとexportを使う標準のモジュールシステムで、依存関係を静的に解析できるのでTree shakingのような最適化が可能です。Node.jsはpackage.jsonの"type": "module"設定や、.mjs・.cjs拡張子でどちらかを判断します。

問題は両方を混ぜるときでした。ESMからCJSをimportするのはずっと前からできましたが、CJSからESMをrequire()するのはできなかったのです。そのためライブラリはCJSとESMの2つをビルドしてexportsフィールドで分けて公開するdual package方式が一般的でした。ビルドも2回、型定義も2セット、バグも2倍です。

{
  "name": "my-lib",
  "type": "module",
  "exports": {
    ".": {
      "import": "./dist/index.js",
      "require": "./dist/index.cjs"
    }
  }
}

これがNode 22.12(2024年12月)から変わりました。require()でESMファイルをフラグなしで読み込めるようになり、Node 20.19にもバックポートされています。ESM一本だけ公開しても、CJSの利用者がrequire()で使えるわけです。唯一の制限はtop-level awaitを使うモジュールです。同期的なrequire()では待てないので、ERR_REQUIRE_ASYNC_MODULEエラーになります。

実務で押さえるのは2つです。新しいライブラリはESM単一ビルドを基本にして、どうしてもサポートが必要な古いNodeバージョンがあるときだけdual packageを維持すること。そしてexportsフィールドの条件の順序(types、import、require、default)を整理しておくと、バンドラーごとに違うファイルを拾ってしまう事故を防げます。

3. package.json:3つの依存関係フィールドとパッケージマネージャー#

まず3つのフィールドの違いを整理します。

フィールド インストールされるタイミング 使う場面
dependencies 常にインストールされます ランタイムに必要なパッケージ
devDependencies 開発環境でのみインストールされます。productionインストールやライブラリ利用者側では外れます ビルドツール、テスト、リンター
peerDependencies 直接インストールせず、利用側がインストールしたものを一緒に使います Reactのようにひとつだけ存在すべきパッケージ

peerDependenciesが一番わかりにくいです。鍵はシングルトンです。UIライブラリがReactをdependenciesに入れると、アプリのReactとライブラリのReactが2つインストールされることがあり、Hookが壊れます。だから「あなたがインストールしたReactを私も使います」と宣言するのがpeerです。npm 7からはpeerも自動でインストールされますが、バージョンが合わないときは警告ではなく潜在的なランタイムエラーだと考えるべきです。

パッケージマネージャーがこれらのフィールドを読んでやることは3段階です。"react": "^18.2.0"のような範囲を正確なバージョンに固定し、その依存関係の依存関係まで固定するResolution(結果がlockファイル)、固定されたバージョンをレジストリから取得するFetch、取得したファイルをソースコードから使えるように接続するLinkです。このうちLinkの方式がパッケージマネージャーごとに違います。

方式 動作 2026年の状況
npm 依存関係をnode_modulesにフラットに引き上げて(hoisting)インストールします 最も基本ですが、宣言していないパッケージが使えてしまう幽霊依存の問題があります
pnpm グローバルストアに一度だけ取得し、hard linkとsymlinkで接続します。宣言したパッケージだけが見えます モノレポのデフォルトのような位置を確立しました。9.5からcatalog:でワークスペース全体のバージョンを一か所で管理できます
Yarn PnP node_modulesなしで依存関係マップをJSファイルに置き、requireがそれを参照します Yarn 4(Yarn Modern)に続いています。厳密ですが、ツールとの互換性コストがあります
Bun 独自ランタイムと統合されたインストーラーです インストールが速く採用も増えていますが、まだNodeベースのCIと混ぜて使うケースが多いです

幽霊依存(phantom dependency)は必ず知っておきたい概念です。npmが重複インストールを減らそうと下位の依存関係を最上位のnode_modulesに引き上げると、自分がpackage.jsonに書いていないパッケージもimportできてしまいます。今は動いていても、依存関係ツリーが変わった日に壊れます。pnpmとPnPが厳密なのは、これを防ぐためです。

もうひとつ、チーム全体で同じパッケージマネージャーのバージョンを使わせてくれていたCorepackが、Node 25からデフォルトの配布物に含まれなくなりました。package.jsonのpackageManagerフィールドでバージョンを固定する習慣はそのまま残しつつ、インストールはpnpmやYarnが案内する独立したインストール方法に従うのが正解です。

4. トランスパイラーとバンドラー:Rustに移ったチェーン#

ブラウザはファイルをひとつずつ受け取ります。でも私たちはファイルを何百にも分けて開発しますよね。その間を埋めるのがバンドラーで、やることは3段階です。

  1. Resolution — import './utils'のようなあいまいなパスを正確なファイルパスに解決します。exportsフィールドや条件付きexportの解釈もここです。
  2. Load(Transform) — TypeScript、JSX、最新構文をブラウザが理解できるJSに変換します。トランスパイラーの仕事です。
  3. Optimization — 使っていないコードを取り除き(Tree shaking、除外はsideEffectsフィールドで指定)、変数名を短くしてパターンを圧縮します(Minification)。

この構造は2024年も今も同じです。変わったのは誰がやるかです。BabelとTerserがやっていた仕事を、RustやGoで作られたツールが引き継ぎました。

  • Vite 8(2026年3月)はesbuildとRollupの組み合わせをやめ、Rust製バンドラーRolldownをデフォルトにしました。Rolldownは2026年5月に1.0が出て、トランスパイルは同じチームのOxcが担当します。複雑なプロジェクトは、Vite 7で先にrolldown-viteパッケージに切り替えてから8に上げることが推奨されています。
  • Next.js 16は開発サーバーとプロダクションビルドの両方でTurbopackがデフォルトです。カスタムwebpack設定があるなら--webpackフラグで戻せます。
  • webpack設定をそのままに速度だけ上げたいなら、webpack互換APIを持つRspackがあります。

React Native側は事情が違います。デフォルトのバンドラーMetroはいまだにTree shakingが限定的です。ExpoがEXPO_UNSTABLE_TREE_SHAKINGのような実験的フラグでプロダクションビルドに限ってサポートしていて、それ以外ではRspackベースのRe.Packが代替として使われています。2024年の講演で、Metroの長いビルドと--reset-cacheの問題のためにesbuildへ自力で乗り換えたチームの話を聞きましたが、当時の問題がまだ全部解決したわけではないということです。

ツールの名前は変わり続けますが、問いは同じです。うちのビルドは3段階のどこで時間を使っているのか? これに答えられれば、次のツールが出てきても乗り換えるかどうか判断できます。

5. CI/CD:変わったものだけビルドし、キャッシュは共有する#

モノレポではビルド数が(変更されたサービス数)×(環境数)で増えます。開発、ステージング、本番を別々にビルドすると、サービスひとつ直しても3回です。私が見た事例では、すべてのビルドを独立したランナーで並列に回すようにパイプラインを作り直し、理想的なケースで6分以内に終わらせていました。今も原則は同じです。パイプラインを独立したコンピューティング環境に分けることが最初です。

2026年にはここに2つが標準でついてきます。

  • 影響を受けるものだけビルド — Turborepoはturbo build --affected、Nxはnx affectedで、main比で変更されたパッケージとその依存先だけを選んで実行します。かつてモノレポツールの代名詞だったLernaは、今はNxチームがメンテナンスしています。
  • リモートキャッシュ — 同じ入力なら、別のランナーでビルドした結果をそのまま持ってきます。TurborepoはVercelのリモートキャッシュかセルフホスト、NxはNx Cloudを使います。キャッシュが当たればビルド自体をスキップするので、体感差が大きいです。

CIプラットフォームはGitHub Actionsが事実上の標準になりました。先ほどのactions/checkoutのpartial cloneオプション、ランナーイメージの事前ビルド、そしてDockerレイヤーキャッシュ(共通レイヤーは前に、頻繁に変わるJSは後ろに)を組み合わせれば、大きなリポジトリでも対応できます。

デプロイ成果物のサイズにも気を配りましょう。Next.jsのstandalone出力のように、ランタイムで実際に必要なファイルだけを追跡して含める機能を使うと、私が見た事例では40GBのモノレポから200MBのデプロイ物ができていました。pnpmやPnPのようにnode_modulesの構造が違う環境では、この追跡がそのままでは動かないことがあるので、事前に確認してください。

6. エラー検知とパフォーマンス:デプロイする前に知る#

最後の項目は「安全に運用する」です。パフォーマンスとエラーの2軸に分けます。

パフォーマンスはWeb Vitalsで#

Googleが定めたCore Web Vitalsの3つが基準です。

  • LCP(Largest Contentful Paint) — 最も大きいコンテンツが描画されるまでの時間。ロード完了の時点とみなします。
  • INP(Interaction to Next Paint) — ユーザー入力から次の画面が描画されるまでの遅延。2024年3月12日にFIDを置き換えました。最初の入力しか見なかったFIDと違い、ページ滞在中のすべてのインタラクションを見て、200ms以下なら良好です。
  • CLS(Cumulative Layout Shift) — 累積レイアウトシフト。

2024年のノートにはFIDが書いてありました。今の計測コードにFIDが残っているなら、INPに変えるべきです。

LCPを減らす大枠は今でもSSRです。クライアントレンダリングはHTMLを受け取り、JSバンドルを受け取り、実行して、ようやくAPIを呼んでからコンテンツが見えます。SSRはサーバーがリクエストヘッダーでユーザーを識別し、近くのAPIサーバーに直接リクエストして、コンテンツ入りのHTMLを一度のリクエストで返します。私が見た事例では、これでLCPの平均が0.9秒まで下がりました。今はここにReact Server ComponentsとストリーミングがReact 19と主要フレームワークの標準として入ってきて、どのコンポーネントをサーバーで完結させ、どれをクライアントに送るかが設計の中心になっています。

その代わりSSRはビルド・デプロイ時間、監視、サーバー費用が増えます。5番のCI/CD最適化が出てきたのは偶然ではありません。

エラーは1分以内に#

フロントエンドでは、SentryのようなSaaS以外にサービスの状態を診断してエラーを即座に察知する仕組みがないチームが多いです。私が学んだチームの目標は明確でした。新しく発生したエラーは1分以内に検知される。 そのために必要なのはツールひとつではなく、組み合わせです。

  • エラー収集ツール + ソースマップ — 圧縮されたバンドルで起きたエラーを、元のコード位置に戻す必要があります。ソースマップのアップロードをデプロイパイプラインに入れておきましょう。
  • 分散トレーシング — フロントエンドのリクエストとバックエンドのログを同じtrace IDで結ぶOpenTelemetry系のツールが標準になりました。「どのAPIが遅くてLCPが押されたのか」をひとつの画面で見るには必要です。
  • カナリアデプロイ — 100台中1台だけに先に反映し、エラー率を見てから全体に広げます。エラーを早く捕まえるというより、エラーが広がらないようにする仕組みです。
  • デプロイ前のパフォーマンス比較 — 遅くなったことをユーザーに教えてもらう前に知る必要があります。PRごとにバンドルサイズとWeb Vitalsを前回のビルドと比較するステップを置けばいいです。

テストは2つのルールを守るだけでも変わります。バグを直すときにテストを書き、最適化するときにパフォーマンスを計測する。 WebのE2EはPlaywrightが標準になりましたし、実際のユーザー操作とテスト操作が違う部分(モバイルで入力時に一緒に発生するクリックイベントなど)があるなら、それはテストコードではなくライブラリのレベルで合わせるのが良いです。

まとめ:6つの問いで点検する#

2年前の募集は終わりましたが、その6行は今自分が使っている開発環境への問いに変えられます。

  1. うちのリポジトリはどうクローンされ、CIのチェックアウトに何秒かかるか?
  2. うちが公開しているパッケージはESM一本か、まだdual packageか?
  3. peerDependenciesに書いたパッケージは本当にシングルトンであるべきものか? 幽霊依存はないか?
  4. ビルドはResolution、Load、Optimizationのどこで時間を使っているか?
  5. サービスをひとつ直すとビルドはいくつ回り、そのうちキャッシュでスキップされるのはいくつか?
  6. 新しいエラーが出たら何分で気づけるか、INPは計測しているか?

ひとつずつ答えていけば、次に直すべきものが自然と見えてきます。ツールの名前は2年後にまた変わっているでしょうが、問いはそのままのはずです。


Until you make peace with who you are, you will never be content with what you have.

— Doris Mortman


他の投稿
パソコンにつなぎっぱなしのAndroidをボット専用機にする 커버 이미지
 ・ 7分

パソコンにつなぎっぱなしのAndroidをボット専用機にする

7か月ぶりに比べ直したPaperclipとMultica 커버 이미지
 ・ 11分

7か月ぶりに比べ直したPaperclipとMultica

AIエージェントの作業を見守る画面を設計してみました 커버 이미지
 ・ 4分

AIエージェントの作業を見守る画面を設計してみました