LoginSignup
7
5

More than 5 years have passed since last update.

【Angular Universal】SSR で 404 を返す ( クライアント側から express Response を操作する方法 )

Last updated at Posted at 2018-04-11

0_g1TwOOdncfeTD2CC.png

解決する問題

存在しないページをリクエストされた場合、通常のサイトであれば Http Status Code を 404: Not Found で返します

しかし、SPA の場合起点となる index.html が問題なく取得できてしまうため、適当に打ったパスにアクセスしようと常に 200: OK が返ってしまいます

これは SPA を開発している人なら誰でも一度は悩まされる問題だと思います
今回は、この問題を Angular Universal で解決します

サーバー側

  • サーバー側は、通常通り @nguniversal/express-engine を使って SSR に備えるだけです
  • 詳細は、公式ドキュメント を参照してください
server.ts
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 へリダイレクトさせておきます
app.routing.ts
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 コンポーネントの作成

  • 「存在しないページです🤖」的なメッセージが表示されるページをイメージしたコンポーネントです
  • シンプルに実装すると こんな感じになるかと思います
not-found.component.ts
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);
        }
    }
}

注目するべき点は、コンストラクタで responseplatformId を受け取っている部分です

    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/commonisPlatformServer 及び isPlatformBrowser を提供してくれているので、platformId を使って現在の実行環境を判定することが出来ます

        if (isPlatformServer(this.platformId)) {
            this.response.status(404);
        }

今回は、サーバーサイドで実行している場合のみ response を操作してステータスコードを 404: Not Found に変更します

結果

  • 適当な URL を入力してみる
  • 無事 404 で Not-Found ページが取得できました

スクリーンショット 2018-04-11 22.37.28.png

まとめ

Angular を Universal 化している場合は、こんな感じでステータスコードを操作できます
今回の例では 404 への変更だけでしたが、実際は 404 以外のステータスコードを返したり、ヘッダーを操作したり…ということがありそうなので、response を操作する処理をまとめたサービスを作成し、そこから this.responseService.setNotFound() のように使うのが良いかと思います

Universal 化を進めるにあたって このリポジトリ が大変参考になっています
今回紹介したレスポンスを操作する処理も綺麗にまとまっているので、現在 Universal チャレンジしている人は参考になりそうですヾ(。>﹏<。)ノ゙

7
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
5