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)
を加えるようになる。このプロバイダー関数の引数にサーバーサイドルートの設定を渡している。
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
として使う文字列を返せば、ビルド時に実際の値が入って個別のページがプリレンダリングされる。
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
が出力されている。
"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"
},
これまでのAngularはクライアントサイドのリッチなシングルページアプリケーションの構築に重心が置かれたフレームワークだったが、v18、v19とサーバーサイドレンダリング方面の強化を強めることでより幅広いWebサイト構築に使えるようになってきた。v19の正式リリースが楽しみだ。ぜひ今まではAngularが向いていないと思われていたユースケースでもいろいろ試してみて欲しい。