Dev Study
← 解説「@angular/ssr — ng add で始めるサーバサイドレンダリング」に戻る

サンプルコードで身につける: @angular/ssr — ng add で始めるサーバサイドレンダリング

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

1ng add が変えるもの一覧

ng add @angular/ssrを実行すると、追加・変更されるファイルが一式表示されます。それぞれの役割を知っておくと、後でエラーが出たときにどこを見ればよいか分かります。

ng add @angular/ssr

# CREATE src/main.server.ts   … サーバ側アプリの起動口
# CREATE src/server.ts        … Node.js(Express)サーバ本体
# CREATE src/app/app.routes.server.ts
#                             … ルートごとの描画方法の宣言
# UPDATE angular.json         … サーバ用ビルドの設定を追加
# UPDATE src/app/app.config.ts
#                             … provideClientHydration などを追加

# 開発中はこれまで通りでOK(SSRで動く)
ng serve

2RenderMode 3種の宣言

app.routes.server.tsで、ルートのパスごとにServer・Prerender・Clientを割り当てます。ワイルドカード(**)でまとめて既定を決め、例外だけ個別に書くのが読みやすい形です。

import { RenderMode, ServerRoute } from '@angular/ssr';

export const serverRoutes: ServerRoute[] = [
  // ビルド時に作り置き(内容が固定のページ)
  { path: '', renderMode: RenderMode.Prerender },
  { path: 'about', renderMode: RenderMode.Prerender },

  // ログイン後の画面はブラウザ描画のまま
  { path: 'mypage/**', renderMode: RenderMode.Client },

  // 残り(商品詳細など)はリクエストごとにSSR
  { path: '**', renderMode: RenderMode.Server },
];

3afterNextRender でブラウザ限定の処理を書く

localStorageやwindowに触る処理は、afterNextRender()に移します。このコールバックはブラウザでの描画後にしか呼ばれないので、サーバ実行時に落ちる心配がなくなります。

import { Component, signal, afterNextRender } from '@angular/core';

@Component({
  selector: 'app-theme-loader',
  template: '<p>テーマ: {{ theme() }}</p>',
})
export class ThemeLoaderComponent {
  theme = signal('light'); // サーバでは既定値で描画される

  constructor() {
    // ブラウザで最初の描画が終わった後にだけ実行される。
    // サーバでは呼ばれないので localStorage に触っても安全
    afterNextRender(() => {
      const saved = localStorage.getItem('theme');
      if (saved) this.theme.set(saved);
    });
  }
}

4prerender するパスをパラメータ付きルートに教える

products/:idのようなパラメータ付きルートをPrerenderにする場合、「どのidのページを作り置きするか」をビルド時に教える必要があります。getPrerenderParamsでidの一覧を返します。

import { RenderMode, ServerRoute } from '@angular/ssr';

export const serverRoutes: ServerRoute[] = [
  {
    path: 'products/:id',
    renderMode: RenderMode.Prerender,
    // ビルド時に呼ばれ、作り置きするidの一覧を返す
    async getPrerenderParams() {
      const res = await fetch('https://api.example.com/products');
      const products = await res.json();
      return products.map((p: { id: number }) => ({
        id: String(p.id),
      }));
    },
  },
];
// → ビルドすると products/1, products/2, ... のHTMLが生成される

5ビルドして本番と同じ形で動かす

SSRアプリのビルドはブラウザ用とサーバ用の2つの成果物を作ります。本番ではdistの中のサーバをNode.jsで起動します。ng serveだけでなく、この形でも一度動作確認しておくと安心です。

# ビルド(ブラウザ用とサーバ用の両方が作られる)
ng build

# dist/my-app/
#   browser/ … ブラウザに配るJS・CSS・画像
#   server/  … Node.jsで動かすサーバ本体

# 本番と同じ形で起動してみる
node dist/my-app/server/server.mjs
# → http://localhost:4000 で、HTMLに中身が入って
#    返ってくることを「ページのソースを表示」で確認する