サーバーサイドレンダリングにAngularUniversalを使用する方法
序章
シングルページアプリケーション(SPA)は、最初にクライアントに提供されるシングルHTMLドキュメントで構成されます。 アプリで必要な新しいビューは、JavaScriptを介してクライアント上でのみ生成されます。 要求と応答のサイクルは引き続き発生しますが、これは通常、データ用のRESTfulAPIに対してのみ行われます。 または、画像などの静的リソースを取得します。
この方法でアプリケーションを作成することには多くの利点があります。 ただし、Webクローラーがアプリをトラバースする機能や、アプリの読み込み中にパフォーマンスが低下するなど、失うものがいくつかあります。これにはかなりの時間がかかる場合があります。 ギャップを埋めるためにサーバーサイドレンダリング(SSR)が登場します。
この記事では、サーバーサイドレンダリングにAngularUniversalを使用する方法を学習します。
前提条件
このチュートリアルに従うには、次のものが必要です。
- Node.jsはローカルにインストールされます。これは、Node.jsのインストール方法とローカル開発環境の作成に従って実行できます。
- Angularプロジェクトのセットアップにある程度精通している。
このチュートリアルは、ノードv16.4.2、npm
v7.19.1、@angular/core
v12.1.1、および@nguniversal/express-engine
v12.1.0で検証されました。
ステップ1—AngularUniversal入門
Angularアプリケーションはシングルページアプリであり、クライアントのブラウザーで実行されます。 ただし、Angular Universalを使用すると、サーバー上でAngularアプリを実行することもできます。 これにより、静的HTMLをクライアントに提供できます。 Angular Universalを使用すると、サーバーはページを事前にレンダリングしてユーザーに何かを表示し、クライアント側のアプリはバックグラウンドで読み込まれます。 次に、すべてがクライアント側で準備が整うと、サーバーでレンダリングされたページの表示からクライアント側のアプリにシームレスに切り替わります。 ユーザーは、「読み込み中」のスピナーが終了するのを待つ代わりに、フル機能のクライアント側アプリケーションの使用を開始できるようになるまで、少なくともユーザーの関心を維持するためのコンテンツを用意できるという事実以外に、違いに気付かないはずです。
Angular Universalを使用するSSRを機能させるには、クライアントアプリケーションとサーバースタックの両方を変更する必要があります。 この記事では、これがバージョン> =7のAngularCLIでほとんど作成されていないまったく新しいAngularアプリケーションであると想定します。 ほぼすべてのサーバーテクノロジーでユニバーサルアプリを実行できますが、それ自体がノードパッケージであるAngularUniversalによって提供される特別な関数renderModuleFactory()
を呼び出すことができる必要があります。 したがって、Node / ExpressサーバーからこのAngularアプリケーションを提供することは、この例では理にかなっています。
回路図を使用して、すばやく立ち上げます。
アプリディレクトリからターミナルを開き、次のコマンドを実行します。
- ng add @nguniversal/express-engine
この回路図がアプリにいくつかの変更を加え、いくつかのファイルを変更し、いくつかのファイルを追加したことに気付くでしょう。
更新angular.json
projects.{{project-name}}.architect.build.options.outputPath
が"dist/browser"
に変わります"server"
と呼ばれる新しいprojects.{{project-name}}.architect
が追加されました
これにより、AngularCLIはサーバー/ユニバーサルバージョンのAngularアプリケーションについて知ることができます。
更新package.json
いくつかの新しい依存関係(予想される)に加えて、いくつかの新しいスクリプトも取得します。
"dev:ssr"
"build:ssr"
"serve:ssr"
"prerender"
更新main.ts
これは、ユニバーサルレンダリングされたページが完全に読み込まれるまで、アプリのブラウザーバージョンがブートストラップを開始しないように変更されました。
更新app.module.ts
インポートされたBrowserModule
で静的メソッド.withServerTransition
を実行するように変更されました。 これは、クライアントがサーバーバージョンからある時点で移行することをアプリケーションのブラウザバージョンに通知します。
server.ts
を作成します
これはNodeJSExpressサーバーです。 明らかに、次の行に注意してくださいが、生成されたとおりにこの正確なサーバー設定を使用する必要はありません。
server.engine('html', ngExpressEngine({ ... }))
ngExpressEngine
は、ユニバーサルを機能させるrenderModuleFactory
のラッパーです。 この正確なserver.ts
ファイルをセットアップに使用しない場合は、少なくともこの部分をコピーして、自分のファイルに統合してください。
tsconfig.server.json
を作成します
これは、ユニバーサルアプリケーションのエントリモジュールを見つける場所をAngularコンパイラに指示します。
app.server.module.ts
を作成します
これは、サーバーバージョンのみのルートモジュールです。 AppModule
と、@angular/platform-server
からServerModule
をインポートし、AppModule
と同じAppComponent
をブートストラップしていることがわかります。 AppServerModule
は、ユニバーサルアプリケーションのエントリポイントです。
main.server.ts
を作成します
この新しいファイルは基本的に、アプリケーションのユニバーサルバージョンのエントリポイントであるAppServerModule
のみをエクスポートします。 これについてはすぐに再検討します。
ステップ2—ユニバーサルアプリを起動する
コマンドラインから、次のコマンドを実行します。
- npm run build:ssr
そして、実行します:
- npm run serve:ssr
ビルドプロセス中に問題が発生しなかった場合は、ブラウザーを開いてhttp://localhost:4000
(または構成されているポート)を開くと、ユニバーサルアプリが動作していることがわかります。 見た目は変わりませんが、最初のページは通常のAngularアプリケーションよりもはるかに高速に読み込まれるはずです。 アプリが小さくてシンプルな場合、これは気づきにくいかもしれません。
Chrome開発ツールを開き、[ネットワーク]タブで、オンラインというドロップダウンを見つけて、ネットワーク速度を調整してみてください。 Slow 3G を選択して、低速ネットワーク上のデバイスを模倣します。ランディングページ、およびアクセスするルーティングされたページのパフォーマンスは引き続き良好です。
また、ページソースを表示してみてください(ページを右クリックして、ページソースの表示を選択します)。 <body>
タグには、ページに表示されているものと一致するすべての通常のHTMLが表示されます。つまり、アプリケーションはWebクローラーによって意味のある形でスクレイピングされる可能性があります。 これを非ユニバーサルアプリケーションのページソースと比較すると、<body>
タグに表示されるのは<app-root>
(またはブートストラップされたAppComponent
)。
結論
この記事では、サーバーサイドレンダリングにAngularUniversalを使用する方法を学びました。
ユニバーサルアプリケーションはブラウザではなくサーバー上で実行されるため、アプリケーションコードで注意する必要があることがいくつかあります。
window
、document
、location
などのブラウザ固有のオブジェクトの使用を確認してください。 これらはサーバーに存在しません。 とにかくこれらを使用するべきではありません。Document
やLocation
などの注射可能なAngular抽象化を使用してみてください。 最後の手段として、本当に必要な場合は、条件付きステートメントで使用法をラップして、ブラウザーのAngularでのみ使用されるようにします。 これを行うには、関数isPlatformBrowser
およびisPlatformServer
を@angular/common
からインポートし、PLATFORM_ID
トークンをコンポーネントに挿入し、インポートした関数を実行して、サーバーまたはブラウザーを使用しています。ElementRef
を使用してHTML要素のハンドルを取得する場合は、nativeElement
を使用して要素の属性を操作しないでください。 代わりに、Renderer2
を注入し、そこにある方法の1つを使用します。- ブラウザのイベント処理は機能しません。 サーバーで実行している場合、アプリはクリックイベントやその他のブラウザイベントに応答しません。 ただし、
routerLink
から生成されたリンクはすべてナビゲーションで機能します。 - 可能な限り、
setTimeout
の使用は避けてください。 - サーバーリクエストのすべてのURLを絶対にします。 サーバーが相対URLを処理できる場合でも、サーバーから実行すると、相対URLからのデータの要求は失敗します。
- 同様に、サーバーから発行されたHTTPリクエストに関するセキュリティは、ブラウザから発行されたものと同じではありません。 サーバーリクエストには、異なるセキュリティ要件と機能がある場合があります。 これらのリクエストのセキュリティは自分で処理する必要があります。
公式ドキュメントでAngularUniversalが提供するものについて引き続き学習してください。