解決する問題
存在しないページをリクエストされた場合、通常のサイトであれば Http Status Code を 404: Not Found
で返します
しかし、SPA の場合起点となる index.html
が問題なく取得できてしまうため、適当に打ったパスにアクセスしようと常に 200: OK
が返ってしまいます
これは SPA を開発している人なら誰でも一度は悩まされる問題だと思います
今回は、この問題を Angular Universal で解決します
サーバー側
- サーバー側は、通常通り
@nguniversal/express-engine
を使って SSR に備えるだけです - 詳細は、公式ドキュメント を参照してください
import { ngExpressEngine } from '@nguniversal/express-engine';
import { provideModuleMap } from '@nguniversal/module-map-ngfactory-loader';
...
const { AppServerModuleNgFactory, LAZY_MODULE_MAP } = require(path.join(DIST_FOLDER, 'main.bundle'));
const server = express();
server.engine('html', ngExpressEngine({
bootstrap: AppServerModuleNgFactory,
providers: [ provideModuleMap(LAZY_MODULE_MAP) ],
}));
...
クライアント側
不明な URL のリダイレクト
- どの定義にもマッチしなかった URL を捕捉し、
not-found
へリダイレクトさせておきます
import { Routes } from '@angular/router';
export const routes: Routes = [
...
{ path: 'not-found', loadChildren: './features/not-found/not-found.module#NotFoundModule' },
{ path: '**', redirectTo: 'not-found' },
];
Not-Found コンポーネントの作成
- 「存在しないページです🤖」的なメッセージが表示されるページをイメージしたコンポーネントです
- シンプルに実装すると こんな感じになるかと思います
import { Component, Inject, OnInit, Optional, PLATFORM_ID } from '@angular/core';
import { isPlatformServer } from '@angular/common';
import { RESPONSE } from '@nguniversal/express-engine/tokens';
import { Response } from 'express';
@Component({
selector: 'app-not-found',
templateUrl: './not-found.component.html',
styleUrls: [ './not-found.component.scss' ],
})
export class NotFoundComponent implements OnInit {
constructor(
@Optional()
@Inject(RESPONSE)
private response: Response,
@Inject(PLATFORM_ID) private platformId: Object,
) {}
public ngOnInit() {
if (isPlatformServer(this.platformId)) {
this.response.status(404);
}
}
}
注目するべき点は、コンストラクタで response
と platformId
を受け取っている部分です
constructor(
@Optional()
@Inject(RESPONSE)
private response: Response,
@Inject(PLATFORM_ID) private platformId: Object,
) {}
RESPONSE
response
は、型を見て分かる通り express で使用するものと全く同じオブジェクトです
RESPONSE
及び REQUEST
は、@nguniversal/express-engine
が自動的に登録してくれているので、どこでも Inject することができます
なお Optional
デコレータはその名の通り、あったりなかったりするプロパティに使います
今回の例では、アプリケーションがサーバー側で起動した場合にのみ response
が存在するため付与してあります
PLATFORM_ID
platformId
は、アプリケーションの実行環境を判定するために使用する値です
@angular/common
が isPlatformServer
及び isPlatformBrowser
を提供してくれているので、platformId
を使って現在の実行環境を判定することが出来ます
if (isPlatformServer(this.platformId)) {
this.response.status(404);
}
今回は、サーバーサイドで実行している場合のみ response
を操作してステータスコードを 404: Not Found
に変更します
結果
- 適当な URL を入力してみる
- 無事 404 で Not-Found ページが取得できました

まとめ
Angular を Universal 化している場合は、こんな感じでステータスコードを操作できます
今回の例では 404 への変更だけでしたが、実際は 404 以外のステータスコードを返したり、ヘッダーを操作したり…ということがありそうなので、response を操作する処理をまとめたサービスを作成し、そこから this.responseService.setNotFound()
のように使うのが良いかと思います
Universal 化を進めるにあたって このリポジトリ が大変参考になっています
今回紹介したレスポンスを操作する処理も綺麗にまとまっているので、現在 Universal チャレンジしている人は参考になりそうですヾ(。>﹏<。)ノ゙