Marginalia

Angular v19のプリレンダリングと静的サイト構築

11月にリリース予定のAngular v19ではサーバーサイドレンダリングの強化がてんこ盛りだが、関連してプリレンダリング機能も強化されている。シングルページアプリケーションのパフォーマンス最適化の域を超えて、静的サイトの構築にも十分使えるほどに拡張されたので、その要点をまとめておく。なお、今回はプリレンダリングに焦点を当て、サーバーサイドレンダリングについてはあらためて書くつもりなのであまり触れない。

この記事はAngular v19の最新ビルド(next.7相当)を前提としており、正式バージョンでは変更されている部分があるかもしれないことには留意されたし。

outputMode : ビルド出力のモード切り替え

Angular v19で大きく変わる点は、Angular CLIのビルドにoutputModeという設定が導入され、static ビルドとserver ビルドが選べるようになることだ。

@angular/build:application ビルダーに追加されたこの設定は、ビルド出力を静的なHTML/JS/CSSだけで構成するWebサイトにするか、サーバーサイドレンダリングを組み込んだWebアプリケーションとするかを選べるようにする。staticモードでビルドした場合はデプロイ先はWebサイトのホスティングサービスやCDNなどになり、server モードでビルドした場合のデプロイ先はNode.js環境やエッジワーカー環境などになるだろう。

静的サイト構築を考えた場合はもちろんstaticビルドを使うことになる。

サーバーサイドルート

outputMode がどちらであっても、ビルド時にプリレンダリングを行うかどうかを決定するのはこのサーバーサイドルートの設定である。これまでAngularのルーティングはクライアントサイドだけのものだったが、Angular v19ではサーバーサイドレンダリングやプリレンダリングのためのルーティング設定を与えられるようになった。

サーバーサイドレンダリング用のアプリケーション設定に、あらたにprovideServerSideRoutesConfig(routes) を加えるようになる。このプロバイダー関数の引数にサーバーサイドルートの設定を渡している。

app.config.server.ts
import { ApplicationConfig, mergeApplicationConfig } from '@angular/core';
import { provideServerRendering } from '@angular/platform-server';
import { provideServerRoutesConfig } from '@angular/ssr';
import { appConfig } from './app.config';
import { routes } from './app.routes.server';

const serverConfig: ApplicationConfig = {
  providers: [provideServerRendering(), provideServerRoutesConfig(routes)],
};

export const config = mergeApplicationConfig(appConfig, serverConfig);

provideServerRoutesConfig 関数の引数の型はServerRoute[]で、クライアントサイドでprovideRouter関数に渡すRoute型とは異なる。このオブジェクトはサーバーサイドレンダリングにおいてURLパスとそのパスのレンダリング戦略をマッピングするためのものである。

次の例のアプリケーションでプリレンダリングするのは、空文字列のルートパスと、users/:idで指定されたユーザー詳細ページのパスの2種類である。サーバーサイドルートでは、この:idのようなパスパラメータに具体的な値を指定してプリレンダリングするように指示できる。そのためのプロパティがgetPrerenderParamsである。バックエンドAPIに問い合わせて取得したユーザーのリストをもとにidとして使う文字列を返せば、ビルド時に実際の値が入って個別のページがプリレンダリングされる。

app.routes.server.ts
import { inject } from '@angular/core';
import { RenderMode, ServerRoute } from '@angular/ssr';
import { UserApi } from './user-api.service';

export const routes: ServerRoute[] = [
  {
    path: '',
    renderMode: RenderMode.Prerender,
  },
  {
    path: 'users/:id',
    renderMode: RenderMode.Prerender,
    async getPrerenderParams() {
      const userApi = inject(UserApi);
      const users = await userApi.getUsers();
      return users.map((user) => ({ id: String(user.id) }));
    },
  },
];

たとえばブログサイトであればCMSから取得した記事一覧データをもとにすべての記事をあらかじめビルドしておける。コンテンツに更新があれば再ビルドしてデプロイするだけでいいため、いわゆるJamstack的な運用が可能になる。長らくAngularの弱点であったSEOの観点でも、プリレンダリング時にページのメタデータを書き込んでおけばJavaScriptの実行なしにクローラに情報を提供できるため、克服したといっていいだろう。

開発者がやることはこれだけである。あとはいつもどおり ng build コマンドでビルドをすれば、生成物のディレクトリにはルートのindex.htmlと、users/:idに対応した個別IDごとのindex.htmlが出力されている。

angular.jsonのbuild設定
  "build": {
    "builder": "@angular/build:application",
    "options": {
      "outputPath": "dist/ng19-ssr-playground",
      "index": "src/index.html",
      "browser": "src/main.ts",
      "polyfills": ["zone.js"],
      "tsConfig": "tsconfig.app.json",
      "assets": [{ "glob": "**/*", "input": "public" }],
      "styles": ["src/styles.css"],
      "server": "src/main.server.ts",
      "outputMode": "static"
    },
ng buildの結果。プリレンダリングされたHTMLファイルが確認できる。
ng buildの結果。プリレンダリングされたHTMLファイルが確認できる。

これまでのAngularはクライアントサイドのリッチなシングルページアプリケーションの構築に重心が置かれたフレームワークだったが、v18、v19とサーバーサイドレンダリング方面の強化を強めることでより幅広いWebサイト構築に使えるようになってきた。v19の正式リリースが楽しみだ。ぜひ今まではAngularが向いていないと思われていたユースケースでもいろいろ試してみて欲しい。