Dev Study
← 解説「初期表示を軽くする — バンドルサイズが最初の敵」に戻る

サンプルコードで身につける: 初期表示を軽くする — バンドルサイズが最初の敵

解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。

1全ルート即時読み込みを遅延読み込みに直す

初期バンドル削減の定番ビフォーアフターです。全ページを最初に読み込む書き方から、利用頻度の低いページだけをloadComponentに切り替えます。機能は既習なので、ここでは「どのルートを遅延させるか」の選び方に注目してください。

import { Routes } from '@angular/router';
import { Home } from './home';

// ── 遅い書き方: 全ページのコードが初期バンドルに入る ──
// import { AdminPage } from './admin/admin-page';
// import { ReportPage } from './report/report-page';
// export const routes: Routes = [
//   { path: '', component: Home },
//   { path: 'admin', component: AdminPage },
//   { path: 'report', component: ReportPage },
// ];

// ── 速い書き方: たまにしか開かないページは遅延読み込み ──
export const routes: Routes = [
  // トップは全員が最初に見るので即時のまま
  { path: '', component: Home },
  // 管理画面・帳票は使う人が限られるので切り離す
  {
    path: 'admin',
    loadComponent: () => import('./admin/admin-page').then((m) => m.AdminPage),
  },
  {
    path: 'report',
    loadComponent: () => import('./report/report-page').then((m) => m.ReportPage),
  },
];

2初期画面内の重い部品を @defer に切り出す

ルート単位では切れない「初期画面の中の重い部品」を@deferで遅らせる例です。ファーストビューの外にあるグラフをon viewportにすると、そのコードは初期バンドルから外れます。ルート遅延と@deferの使い分けの典型例です。

import { Component } from '@angular/core';
import { SalesChartComponent } from './sales-chart.component';

@Component({
  selector: 'app-dashboard',
  imports: [SalesChartComponent],
  // ページ上部の数字はすぐ表示し、スクロールしないと見えない
  // グラフ(描画ライブラリ込みで重い)は viewport 到達まで遅らせる
  template:
    '<h1>今月の売上</h1>' +
    '<p>{{ total }}円</p>' +
    '@defer (on viewport) {' +
    '  <app-sales-chart />' +
    '} @placeholder {' +
    '  <div class="chart-skeleton">グラフを準備中...</div>' +
    '}',
})
export class DashboardComponent {
  total = 1234567;
}

// 効いたかは ng build の Lazy chunk files にグラフ用の
// チャンクが分かれて出ることと、Initial total の減少で確認する

3angular.json の budgets で肥大化を検知する

初期バンドルのサイズ上限を宣言するbudgets設定です。警告ラインとエラーラインの2段構えにしておくと、じわじわ太る変化に警告で気づき、明確な超過はCIで止められます。数値はまず現状より少し厳しい値から始めるのがコツです。

// angular.json(抜粋)。projects.my-app.architect.build.configurations.production
{
  "configurations": {
    "production": {
      "budgets": [
        {
          "type": "initial",
          "maximumWarning": "500kB",
          "maximumError": "1MB"
        },
        {
          "type": "anyComponentStyle",
          "maximumWarning": "4kB",
          "maximumError": "8kB"
        }
      ]
    }
  }
}

// type: "initial" が初期バンドル全体、
// "anyComponentStyle" は各コンポーネントのCSSサイズの上限

4budgets 超過時のビルド出力と対応

budgetsに引っかかったときにng buildが出すメッセージと、その後の動き方です。エラーで止まったら上限を緩めるのではなく、まず何が増えたのかを調べるのが正しい順番です。上限の変更はチームで合意してから行います。

ng build

# ── 警告ラインを超えたとき(ビルドは成功する) ──
# ▲ [WARNING] bundle initial exceeded maximum budget.
#             Budget 500.00 kB was not met by 34.10 kB
#             with a total of 534.10 kB.

# ── エラーラインを超えたとき(ビルドが失敗し、CIも落ちる) ──
# ✘ [ERROR] bundle initial exceeded maximum budget.
#           Budget 1.00 MB was not met by 120.55 kB
#           with a total of 1.12 MB.

# 超過したときの対応順:
#   1. 直近で追加したライブラリ・機能を思い出す
#   2. source-map-explorer で何が増えたかを特定する
#   3. 遅延読み込み・部分 import・代替ライブラリを検討する
#   4. どうしても必要な増加なら、合意の上で budgets を更新する

5遅延させすぎの悪い例 — 分割にもコストがある

何でも遅延にした結果、かえって体験が悪くなる例です。毎回使うページを分割すると、遷移のたびに読み込み待ちが発生します。「初回に必ず通る画面は即時、たまの画面だけ遅延」という判断基準を示す反面教師です。

import { Routes } from '@angular/router';
import { Home } from './home';
import { OrderList } from './order-list';

// ── 悪い例: 毎回使うページまで遅延にしてしまう ──
// export const routes: Routes = [
//   { path: '', loadComponent: () => import('./home').then((m) => m.Home) },
//   { path: 'orders',
//     loadComponent: () => import('./order-list').then((m) => m.OrderList) },
// ];
// => 業務で1日中往復する注文一覧が、開くたびに一瞬待たされる

// ── 良い例: 主要動線は即時、周辺ページだけ遅延 ──
export const routes: Routes = [
  { path: '', component: Home },
  { path: 'orders', component: OrderList }, // 主要動線は即時読み込み
  {
    path: 'settings', // 設定は開く頻度が低いので遅延
    loadComponent: () => import('./settings').then((m) => m.Settings),
  },
];

// 判断に迷ったら計測に戻る: Lighthouse の初期表示と
// 実際の遷移の体感、両方を見て決める