- はじめに
- サーバーレスアプリケーションにおけるフロントエンド
- SPAの課題
- サーバーサイドレンダリング
- Jamstack
- サーバーフル? サーバーレス?
- サーバーレスでサーバーサイドレンダリング
- Nuxt.jsでの例
- 次回
はじめに
サーバーレスでサーバーサイドレンダリングの話です。ReactとかVueを使ったシングルページアプリケーション(SPA)を開発している人がサーバーサイドレンダリングやりたいんだけどサーバーレスでどうやるのって話です。
今回も『サーバーレスアンチパターン今昔物語』というイベントのための記事となっています。
serverless-newworld.connpass.com
なお、今回は前編と称してそもそものところを簡単に説明しつつ、サーバーレスでやる場合の基本的な話を説明していきたいと思います。次回、後編で実際にサンプルアプリを用意して動くもので説明をしていきます。
なお、最初にサーバーサイドレンダリングやJamstackってなんぞやというおさらいを簡単にしておきますが、残念ながら僕はフロントエンドについては少し嗜んでいる程度であってその道のプロではないので間違ってるところがあるかもしれません。
あと、本題に関係ないところは大幅に割愛しています。また、Angularに関しては全く触れていないです、すまん。
サーバーレスアプリケーションにおけるフロントエンド
サーバーサイドレンダリングとかの話の前にフロントエンドとサーバーレスアプリケーションの話をおさらいします。
サーバーレスアプリケーションというのはAWSの場合はFunction as a ServiceであるAWS Lambdaなどを使って作ったアプリケーションのことを指しています。
一応ことわっておくと、サーバーレスなサービスってのは何も本当にサーバがないわけではなくて、あくまでもユーザが管理しなければいけないインフラ要素をなるだけ少なくしたサービスってことです。なのでユーザはビジネスロジックに近いところにフォーカスできる、自分たちでやる必要のある付加価値になりにくい作業を減らせるというのがメリットです。
そんなサーバーレスのサービスですが、世の中にはいろんなベンダーがいろんなサービスを提供しています。AWSであれば前述のAWS LambdaだけでなくAPIを公開するためのAmazon API Gatewayであったりステートマシン制御のためのAWS Step Functionsであったり、その他にもいくつかあります。
さて、そんなサーバーレスのサービスを組み合わせて開発したアプリケーションのことをサーバーレスアプリケーションと呼んだりするわけですが、これにはWebシステムも含まれます。ただし、従来からある3層構造のWebシステムではなく、AWS Lambdaでロジックを実装し、Amazon API GatewayでそれをいわゆるREST APIとして公開する場合が多いです。そしてこのとき、クライアントサイドはいわゆるシングルページアプリケーション(SPA)という形を取ることが多いです。
REST API以外だとAWS AppSyncというサービスを使ってGraphQLで実装するケースもあります。
SPAが何かってのは簡単に言うと単一のWebページで構成されるWebアプリケーション、という感じですか。一応メリットもデメリットもあったりするのですがそれは本題ではないのでここでは割愛します。そしてSPAを開発するにはJavaScriptを用います。よく使われるフレームワークとしてReactとかVue、Angularあたりがあります。
SPAの課題
そんなSPAなんですが少しだけ課題があります。よく言われるのはSEOの問題です。
何が問題かというとSPAってのは先ほど述べたとおり1枚のHTMLに置かれたJSのアプリケーションですべてが成立しています。HTMLがリクエストされてもJSがロードされて処理が実行されるまでは真っ白です。ここ大事。
さて、誤解をおそれずにざっくりと説明すると、検索エンジンというのは特定のページに対してクローラー(bot)が実際にリクエストして内容を取得することでインデックス化されています。
それを踏まえてSPAだと何が起こるか。
検索エンジンのクローラーが仮にJavaScriptを解釈しないものだと真っ白な一枚の何もないものを受け取るだけです。
現在のところGoogleのクローラー(Googlebot)はJavaScriptの解釈と実行も可能です。Googlebotの場合、アクセスしたサイトがJavaScriptで作られたサイトだった場合にJavaScript実行後の実際のコンテンツを取得するためにJavaScriptの実行を試みます。
このときGooglebotはもちろんHeadless ChromiumなレンダリングエンジンでJavaScriptを実行するんだけど、昨年まではChrome41相当の結構古いやつだったのでそもそも正しく動いてくれない、つまり正しいHTMLを認識してくれないという問題があった。
でも、これに関しては昨年のGoogle I/Oでアップデートが発表されて最新のChromeに常に対応していくことが発表されています。なのでこれに関しては以前のような問題はなくなったと言えます。
じゃ、サーバーサイドレンダリングいらないじゃん?って話になるかと思いきやそんなことはないです。SPAのもう一つの課題としてファーストビューが遅いという問題があるんですね。これは人間に対してもそうなんですがbotに対しもです。例えば非同期に他のAPIをコールしてデータを取得してコンテンツを生成する場合ってあると思うんですが、ローディング中の表示やスピナを表示してるケースってあると思いますがbotはそういうのを待ってくれないケースが多いです。
というわけでこういった点がSEO戦略としてはかなり致命的だったと言えます。
あと、そもそもGoogle以外のbotではJavaScriptを解釈して実行してくれないものもあるかと思います。
そういった場合にサーバーサイドレンダリングを行うことで、レンダリング済の正しいページをクローラーが読み取れるのでSEOが向上すると言われています。
サーバーサイドレンダリング
では、サーバーサイドレンダリングとは何か。
これは荒っぽく言うとリクエストがあったときにJSをダウンロードしてクライアント側でレンダリングするのではなく、初回のリクエストはサーバ側でレンダリングしてしまいレスポンスするということです。
こうすることでつまり、先ほど説明したようなクローラーによるアクセスであっても正しいコンテンツを返せるようになると、そういうわけです。
本当はいろいろともっと細かい話があるのだけど今回の話的にはこのくらいの理解でOKです。
このあたりに対応しているフレームワークがReactだとNext.js、VueだとNuxt.jsが有名です。
サーバーサイドレンダリングやるのに必要になるのは当然サーバーサイドでレンダリングするためのサーバです。
Jamstack
少し似たものとしてついでに紹介しておくと、最近注目を浴びているのがJamstackです。JamstackなのかJAMStackなのか、はたまたJAMstackなのか表記が揺れててわからないけど、ここではJamstack.orgの表記にあわせます。
JamstackとはJavaScript、API、Markupというスタックからなるアプリケーションのことですね。すごく乱暴に言うとアプリケーション的な処理はJavaScriptでクライアント側でやり、いわゆるサーバーサイドの処理やデータストアといったものはAPIとして利用、そして作成されたものは静的なファイルとして出力してしまう、というものです。
つまりJavaScriptとAPIでアプリケーションを開発し、それを静的サイトジェネレータを使って静的ファイルとして出力します。
この場合、サービスとして提供するのに必要になるものは静的サイトをホスティングするサーバです。
というわけで静的サイトジェネレータ関連のフレームワークというとReactだとGatsbyが有名ですね。Vue.jsだとVuePressとかGridsomeとか。実はサーバーサイドレンダリングのフレームワークとしてあげたNext.jsやNuxt.jsもこのあたりできます。
じゃ、なんでGatsbyとか使うかっていうとNext/Nuxtあたりと比べて静的サイトジェネレータに特化しているのでよりシンプルとかそんな感じかなーと個人的には思っております。
ちなみにGridsomeについては僕も試したときの記事を書いてます。興味ある方はどうぞ。
サーバーフル? サーバーレス?
まず、JAMStackの場合はとても簡単な話ですね。出力した静的ファイルをどうやって公開するかってだけです。普通にWebサーバ立てるのもありなんでしょうけど、Vercel、NetlifyとかS3のStatic site hosting機能なんかを使って公開することが多いと思います。こういうの使うってのはつまりサーバーレス。
ちなみにJamstackの場合はどこで公開するかってのもあるけど、それよりもGitでPushしてからの静的サイト生成してからのデプロイっていういわゆるデプロイパイプラインをどう作るか、どんな感じでサポートされているかってところのほうが重要だったりするかと個人的には思う。
この辺りは前述のGridsomeで作ったサイトをAmplify Consoleでデプロイしてるブログ書いてるので、こちらも興味ある方はどうぞ。
で、ようやく本題。
割と面倒なのがサーバーサイドレンダリングをやりたい場合ですね。ちなみにこちらの場合もHTMLやCSSといった静的なファイルはNetlifyとかS3とか使えばいいですね。
サーバーサイドレンダリングの場合は、先ほど述べたようにサーバーサイドでレンダリングするのでそのためのサーバが必要になります。Node.jsを入れたサーバとかですね。
最近だとよくあるのはNode.jsを入れたサーバをコンテナで用意するパターンでしょうか。コンテナでやるのもいいのですがそれだとランタイムだのなんだのって管理しなければいけなくなります。
また、そもそもコンテナを実行するための基盤も必要になりますよね。
あとはサーバーサイドレンダリングのインフラ的な課題としてこんなのがあります。
- CPU負荷が高くなりがち
- CPU負荷高いのでさばけるリクエスト量が少なくなりがち
- キャパシティ不足になってしまうとレスポンスが返せなくなることがあり、そうなるとブラウザ上では真っ白が画面に…
他にもブラウザとNode.jsの両方で動くことを想定した実装が必要とかいろいろありますがそのあたりは今回は割愛。
言いたいことはサーバーサイドレンダリングはインフラ的な課題が発生するということですね。
というわけでサーバーレスです。
サーバーレスでサーバーサイドレンダリング
さて、サーバーレスといっても世の中にはいろいろありますが、ここでは基本的にAWSのサーバーレスを前提に話を進めていきたいと思います。つまりAWS Lambdaとかそのあたりですね。
サーバーサイドレンダリングやるのにLambdaを使うってのはさっき言ったコンテナとかインフラそのものの管理をしたくないとかっていうそういうところもあるんですが、CPU負荷が高いところとそれによるスケーラビリティ上の難点を解決してくれるのではってところがあります。
Lambdaの場合、1リクエスト(=イベント)に対して1インスタンス(=コンテナ)が対応します。同時にリクエストが来た場合もそれぞれ別のインスタンスが対応することになります。なのでリクエストが集中してサーバに処理が集中してCPU負荷が高まった結果クライアントにレスポンスが返せなくなるみたいなことが発生しないんですね。
というわけでLambdaでやるわけですが、LambdaでやるといってもそのLambdaを呼び出すために手前にAPI Gatewayを用意します。
あと、キャッシュは必須ですね。なのでCloudFrontも併用します。つまり、キャッシュヒットしないやつはAPI Gatewayを経由してLambdaでサーバーサイドレンダリングされる感じになります。
それ以外の画像系とかその手の静的ファイルはCloudFrontで振り分けた上でS3を使って配信します。
あれ、そうすると普通のSPAとあまり変わらないってことに気づいたかと思います。そうです、つまり気にするのはLambda上の実装だけになります。
Nuxt.jsでの例
さて、ここからはNuxt.jsでサーバーサイドレンダリングする場合を例として書いていきます。といっても実はとてもシンプルです。
まずクライアント側は普通に作ります。nuxt.config.js
をちょっといじってあげる必要はあります。これは次回に。
そしてサーバー側はExpressを使います。その上で必要になるのがaws-serverless-express
というライブラリです。これはExpressで書かれたアプリをLambdaで実行可能にするためのラッパーのようなものです。
これを使ってやることは単にモノリシックなLambdaファンクションとしてデプロイして、すべてのリクエストに対してそれを経由させてExpressで作られたアプリケーションを呼び出すってだけです。
というわけでExpressで動くNuxt.jsのアプリを用意した上で、LambdaファンクションをNode.jsで作って前述のaws-serverless-express
を利用してExpressをLambdaファンクション上で実行する、つまりNuxtによるサーバーサイドレンダリングをLambda上で実行する。と、そういうわけです。
なお、そのためにLambdaファンクションとしてのエントリポイントとなるhandler関数を用意して、そこからNuxtのコードを呼び出していく感じになります。
以下はサンプルそのままのhandler関数ですね。
'use strict' const awsServerlessExpress = require('aws-serverless-express') const app = require('./app') const server = awsServerlessExpress.createServer(app) exports.handler = (event, context) => { awsServerlessExpress.proxy(server, event, context) }
ここでcreateServer
に渡しているapp.js
がありますが、ここにNuxtとExpressを使うもろもろを記述していきます。が、そのあたりは時間切れとなってしまったので次回に。
次回
というわけで後編に続きます。
今回は最後にちょろっと説明しただけで実装面は何も説明しませんでしたが、後編では実際にサーバーレスでの実装をソースコードつきで具体的にお見せしていきたいと思います。
ちなみにVue.jsとNuxt.jsでやりますが、ReactとNext.jsでやりたい場合はServerless FrameworkのServerless Next Component
というのを使えば良さそうです。これは試したことないんですがいつか試してみようと思います。