DMM.comの、一番深くておもしろいトコロ。

Nuxt.js v2とGAE/SE Node.jsでSPA×SSR×PWA×サーバーレスを実現する

Nuxt.js v2とGAE/SE Node.jsでSPA×SSR×PWA×サーバーレスを実現する

はじめに

こんにちは。イベント事業部の上井(ウワイ)と申します。

Nuxt.jsのv2が9月に正式バージョンとしてリリースされました 🎉
medium.com

またそれに先立って、Google Cloud PlatformからGoogle App Engine Node.js Standard Environment(以下、GAE/SE)が6月に一般公開されました。GCPからサーバーレスでNode.jsが実行できるPaaSの登場です。さらには、つい先日にLTSとなったNode.js 10に対応しました!
cloud.google.com

今回は、Nuxt.js v2のPWAをGAE/SE上で動作させる方法をご紹介いたします。

私が以前書いた記事では、Firebase上でNuxt.jsを動作させたのですが、いかんせん構成が複雑でコードの実装・管理が大変でした。しかし、GAE/SEの場合はシンプルな構成のため、コードにも恩恵が生まれました。なおかつ、ベンダーロックインも回避できます。

Nuxt.js v2 & PWA

Nuxt.jsは、ユニバーサルなVue.jsアプリケーションを作るためのフロントエンドフレームワークです。

簡単に言えば、サーバーサイドレンダリングや静的なVue.jsアプリケーションを簡単に作ることができるスグレものです。Vuexストアは標準で利用可能、ミドルウェアやモジュールなど開発を手助けしてくれる多くの機能を搭載しています。
プレーンなVue.jsよりも簡単に、早く、先進的なアプリケーションを作ることができるのですが、v2では多くのアップデートがなされ、これらのメリットがさらに大きくなりました!

Single Page Application(SPA, シングルページアプリケーション)

SPAとは、その名のとおり単一ページで構成されるWebアプリケーションです。
単一ページでありながら、JavaScriptによる動的レンダリングやHTML5 History APIを用いることで、まるでネイティブアプリケーションのような挙動を実現します。ページ遷移はせず動的にレンダリングを行うため、ユーザー操作のリアクションが非常に高速という特徴を持ちます。さらに、サーバーと通信を行う部分は、通常のWebページと違い必要な情報だけを読み込めば良いため、通信量の削減やレンダリングまでの大幅な時間短縮が可能です。

Server Side Rendering(SSR, サーバーサイドレンダリング)

単なるSPAは、ファーストビューのローディング時間が長いという欠点があります。また、クローラーがJavaScriptで動的にレンダリングされる箇所を正しく解釈できないために、SEO的にも問題があると言われています。 それらの弱点を解消するためのアプローチの1つがSSRです。

SSRは、本来クライアント側でレンダリングする箇所を、サーバー側でレンダリングしてクライアントに返す仕組みです。一般的にSSRの実装コストは高いと言われていますが、Nuxt.jsであれば手軽にSSRが実現できます。

Progressive Web Apps(PWA, プログレッシブウェブアプリ)

PWAとは、Webアプリケーションでありながら、ネイティブアプリケーションの利点を兼ね備えたものです。Service Workerという、クライアントのバックグラウンドで動作するスクリプトによって実現されます。最近では、WindowsのChrome 70がデスクトップPWAに対応したことが話題になっていましたね。詳しくは下記リンクをご参考ください。

PWAに対応することで、主に次のようなメリットが生まれます。

  • オフラインでも閲覧可能
  • インストール可能
  • プッシュ通知が実装可能

Nuxt.jsにはPWAモジュールというものが用意されています。一見ハードルが高そうなPWA対応も、これを用いることで簡単に導入が可能です。

これは個人的な意見ですが、PWAに完全に準拠せずとも、強力なキャッシュ機構を備えたService Workerの恩恵を享受するためにPWAモジュールを導入するのもアリかと思います。

GAE/SE Node.js

f:id:uwai-shinnosuke:20180625181826p:plain:h120

Google App Engineは、Googleのインフラ上でアプリケーションを実行できるPaaS(Platform as a Service)です。いわゆるサーバーレスの実行環境となります。 GAE/SEが持つ高速で柔軟なスケーリング、Blue-Green Deployment、CDN配信、ログ基盤との連携……といった数多の恩恵を享受できます。
Node.jsは、バージョン8と10のランタイム環境が提供されています。

運用レス

サーバーを持つのって、辛いですよね。
死活監視、ログ収集、アップデート、プロビジョニング、スケーリング、デプロイ……パッと思いつくだけでも、考えなければならないことは山ほどあります。
複雑な処理を行うバックエンドならまだしも、フロントエンドのためだけに面倒事を引き受けるのは気が引けますよね。

GAE/SEはフルマネージドのサーバーレス実行環境です。上で挙げたような面倒事はGoogleに任せられます。我々開発者はインフラに手間を掛けることなく、アプリケーション開発に集中することができるのです。

高速・柔軟なスケール

GAE/SE Node.jsのインスタンスは、トラフィックに応じて瞬間的にスケーリングします。急なトラフィック増にも問題なく対応できます。もちろんトラフィックが少ない時はインスタンスも合わせて減るため、無駄な課金も発生しません。Webサービスの高可用性と費用削減を実現できます。

CDN配信

GAE/SEではエッジキャッシュが利用可能で、CDN配信機構が備わっています。 静的コンテンツをCDN配信することで、GAEインスタンスに負荷をかけることなく、ユーザーに速くコンテンツを提供できます。
このエッジキャッシュサーバーはGoogleのインフラ上にあり非常に強力、しかも料金は恐ろしいほど低額です。

HTTPS & HTTP/2

HTTPSの対応も非常に簡単です。
HTTP/2に関してはデフォルトで対応しています。

東京リージョン提供

GAE/SEは東京リージョン(asia-northeast1)で提供されています。国内向けのWebサービスでも安心して利用できますね。

低価格

一般的に、サーバーを持つよりも費用が安く抑えられます。
単純にインスタンスが低価格なことに加え、高速・柔軟なスケールという特長を持ち合わせているためです。上記のようなベネフィットもあるため、費用対効果が非常に高く感じられますね。

HowTo

さて、前置きが少々長くなりましたが、ここから実装方法の解説に入ります。

利用しているNode.jsとnpmのバージョンは下記のとおりです。GAE/SEに合わせてNode.js 10を使用しましょう。

  • Node.js 10.13.0
  • npm 6.4.1 (Yarnでも可)

また、GitHubにソースコードを上げてありますのでご参考いただければ幸いです(若干異なる部分はあります)。

github.com

Nuxt.js v2プロジェクト作成

Nuxt.js v2から導入されたcreate-nuxt-appを利用してプロジェクトを作成してみましょう。

$ npx create-nuxt-app プロジェクト名

# Yarnを利用する場合
$ yarn create nuxt-app プロジェクト名

この際、対話形式でプロジェクト設定が行われます。
f:id:uwai-shinnosuke:20181021014608p:plain:w400

Project name: プロジェクト名

適宜入力してください。

Project description: プロジェクト概要

適宜入力してください。

Use a custom server framework: サーバーフレームワークの選択

次のうちから選択します。今回はNuxt.jsのデフォルトサーバーを利用するためnoneを選択します。

サーバーのカスタマイズを求める方は、お好みのフレームワークを選択してください。

余談ですが、FeathersやMicroといったニッチなフレームワークも選択できることに驚きです。

Use a custom UI framework: UIフレームワークの選択

次のうちから選択します。本記事ではとりわけ扱わないためnoneを選択しましたが、利用したいものがあれば選択すると良いでしょう。

人気のUIフレームワークが揃っているので、それを初期導入できるのは楽ですね。

Choose rendering mode: レンダリングモードの選択

SSR or SPAを選択します。SSRするには必ずUniversalを選択してください。

  • Universal
  • Single Page App

Use axios module: Nuxt Axiosモジュールの利用

Nuxt Axiosモジュールを利用するかどうか選択します。

  • no
  • yes

AxiosはポピュラーなHTTPクライアントパッケージです。そのNuxt.js用モジュールを初期導入できます。アプリケーションにAjaxを使うならyesを選択して良いでしょう(本家とは異なる記述方法であったり、モジュール自体の情報が少ないのが玉にキズですが……)。

Use eslint: ESlintの利用

ESlintは構文チェックツールです。導入するのがベターです。

  • no
  • yes

Use prettier: prettierの利用

prettierはコード自動整形ツールです。こちらも導入するのがベターでしょう。

  • no
  • yes

Author name: 作者名

適宜入力してください。

Choose a package manager: パッケージマネージャの選択

npm or Yarnの選択です。ご利用の方を選択してください。なお、本記事ではnpmで解説を進めます。

  • npm
  • yarn

以上の質問に答え終わるとプロジェクトディレクトリが作成されます。

ここでいったん、動作確認をしてみましょう。
プロジェクディレクトリで下記コマンドを実行します。

$ npm run dev

create-nuxt-app v2.1.1時点のバグで、1件構文エラーが発生します。
pages/index.vue<style>直下の空行を削除することで解消します。

http://localhost:3000/にアクセスします。
f:id:uwai-shinnosuke:20181021013511p:plain:w600

たったこれだけでNuxt.jsプロジェクトの作成は完了です。

PWA化

先述のとおり、Nuxt.jsにはPWAモジュールが用意されているのでこれを用います。公式ドキュメントに沿ってセットアップしましょう。

まずはインストールです。

$ npm install --save @nuxtjs/pwa

そして、nuxt.config.jsにPWAの設定を追記します。

module.exports = {
  ...
  modules: [
    '@nuxtjs/pwa'
  ],
  manifest: {
    name: 'プロジェクト名',
    lang: 'ja'
  },
  ...
};

.gitignoresw.*を追加しておきましょう。これはビルド時に生成されるService Worker系のファイルです。

# Service Worker
sw.*

あとは、staticディレクトリにPWA用のアイコン画像icon.pngを設置してください。

それでは動作確認です。デフォルト設定でPWAは開発モードでは動作しないため、本番モードでビルド、サーバー起動します。

# ビルド
$ npm run build

# サーバー起動
$ npm start

PWA化が完了しました。Service Workerが動作しています。
f:id:uwai-shinnosuke:20181021014735p:plain

ちなみに、開発モードでもPWAを動作させる方法はあるのですが、Service Workerによるキャッシュが開発の妨げになることもしばしばあるため、あまり推奨はいたしません。

以上で、Nuxt.js v2でSSRとPWAが実装できました。

GAE/SEセットアップ

詳細は公式ドキュメントをご参照ください。

Quickstart for Node.js in the App Engine Standard Environment  |  App Engine standard environment for Node.js docs  |  Google Cloud

GCPプロジェクト作成

GCPのコンソールから新しいプロジェクトを作成してください。
f:id:uwai-shinnosuke:20181021013400p:plain:w400

この時に割り当てられるプロジェクトIDは後ほど利用します。

GAE有効化

プロジェクトの作成が完了したら、左サイドバーの[App Engine]よりダッシュボードに移ってください。
「初めてのアプリへようこそ」からNode.jsを選択し、セットアップを進めます。
f:id:uwai-shinnosuke:20181020214052p:plain:w300

リージョンの選択です。GAEのリージョンは後から変更できませんので、慎重に選択してください。東京リージョンはasia-northeast1です。
f:id:uwai-shinnosuke:20181020214057p:plain:w500

「次へ」で進むと、約1分後にGAEの準備が完了します。チュートリアルの案内が表示されますが、無視して構いません。
f:id:uwai-shinnosuke:20181021015035p:plain:w400

Google Cloud SDKのセットアップ

GAEにアプリケーションをデプロイするため、Google Cloud SDKをマシンにセットアップしてください。 セットアップ方法は公式ドキュメントをご参照ください。
Google Cloud SDK Documentation  |  Cloud SDK  |  Google Cloud

セットアップが完了したら、Google Cloud SDKに先ほど作成したプロジェクトを設定します。

$ gcloud config set project GCPプロジェクトID
Updated property [core/project].

app.yaml設置

ここが最も重要でハマりポイントが多いところです。

app.yamlはGAE/SEのサーバーの振る舞いを定義する必須ファイルです。 多くのオプションが用意されているので、詳しくは公式ドキュメントをご参照ください。
app.yaml Configuration File  |  App Engine standard environment for Node.js docs  |  Google Cloud

Nuxt.jsプロジェクトディレクトリ直下にapp.yamlを設置してください。

runtime: nodejs10

instance_class: F2

handlers:
  - url: /_nuxt
    static_dir: .nuxt/dist/client

  - url: /(.*\.(gif|png|jpg|ico|txt))$
    static_files: static/\1
    upload: static/.*\.(gif|png|jpg|ico|txt)$

  - url: /sw.js
    static_files: static/sw.js
    upload: static/sw.js

  - url: /.*
    script: auto
    secure: always

env_variables:
  NUXT_HOST: '0.0.0.0'
  NUXT_PORT: '8080'

上の例の各オプションすべてにポイントがあるので解説していきます。

インスタンスクラス

instance_class: F2

GAE/SEのデフォルトインスタンスクラスはF1ですが、それよりも1つ大きなF2の使用をオススメします。
Nuxt.jsアプリケーションが小さな時は大丈夫なのですが、少し実装が進むと途端にメモリ不足で落ちます。F1のメモリは128MBしかありませんが、F2ならば256MBあるので十分に動作するでしょう。

しかし、料金が高くなったり、無料利用枠内でGAE/SEを利用することが難しくなるのが難点ですかね。インスタンス起動時間を減らすために、オートスケーリングオプションを指定するのも一つの手です。下のオプション例では、一定時間アクセスがない時にインスタンス数を0にしてくれます。

automatic_scaling:
  min_instances: 0

HTTPS化

PWAはHTTPSが必須です。GAE/SEなら簡単に対応できます。
handlersオプションの各項目にsecure: alwaysを指定しています。これにより、すべてのアクセスがHTTPSにリダイレクトされます。

静的コンテンツのStatic Server利用とCDN配信

次のhandlersオプションでは、クライアント側から直接アクセスされる静的コンテンツを、GAE/SEのStatic Serverから配信するよう指定しています。URLとディレクトリをマッピングしています。

handlers:
- url: /_nuxt
  static_dir: .nuxt/dist/client
  secure: always

- url: /(.*\.(gif|png|jpg|ico|txt))$
  static_files: static/\1
  upload: static/.*\.(gif|png|jpg|ico|txt)$
  secure: always

- url: /sw.js
  static_files: static/sw.js
  upload: static/sw.js
  secure: always

Static Serverから配信することで、GAE/SEのインスタンスの負荷を低減することができます。
メリットはそれだけではありません。Static Serverに乗せたファイルは自動的にエッジキャッシュサーバーから配信されます。つまり、静的コンテンツがCDN配信されるということです。Static Serverから配信されたファイルのHTTPレスポンスヘッダにはcache-control: public, max-age=600が付与されており、最大10分間エッジキャッシュサーバーにキャッシュされます。この時間を変更する場合には、default_expirationexpirationオプションを指定してください。

ただし、注意点があります。十分ご確認ください。

  • GAEエッジキャッシュは明示的に削除する方法がありません。
    max-ageが大きな値になっていると、キャッシュがずっと残り続ける悲劇が起こります。意図せずしてこのようなことが起こる可能性もあるので、ご注意ください。
  • cache-control: public, max-ageが付与されているレスポンスはすべてエッジキャッシュに乗ります。
    GAE/SEから返すレンスポンスすべてが対象です。誤って動的コンテンツをキャッシュさせてしまうと、「リリースしたのに反映されない」「レイアウト崩れが発生する」といった問題が起こります。
    Nuxt.jsの場合は、.nuxt/dist/clientstaticディレクトリ配下のファイルのみキャッシュさせるのが安全でしょう。

ホストとポート

env_variables:
  NUXT_HOST: '0.0.0.0'
  NUXT_PORT: '8080'

大前提として、GAE/SE Node.jsではホスト0.0.0.0、ポート8080でサーバを起動する必要があります。
app.yamlのenv_variablesオプションでは環境変数を定義できます。ここで定義した環境変数NUXT_HOST,NUXT_PORTをNuxt.jsのデフォルトサーバーが読み、このホスト・ポートでサーバーを起動するという仕組みです。

なお、Nuxt.jsプロジェクト作成時にサーバーフレームワークを指定した場合には、次の設定で動作するかと思います(Expressのみ確認済み)。

env_variables:
  HOST: '0.0.0.0'
  PORT: '8080'

デプロイ🚀

最後はデプロイです。GAEはデプロイも非常に簡単で、プロジェクトディレクトリで次のコマンドを実行するだけです。

$ gcloud app deploy

デプロイには数分かかります。

Services to deploy:

descriptor:      [/path/to/nuxt2-pwa-gae-se/app.yaml]
source:          [/path/to/nuxt2-pwa-gae-se]
target project:  [nuxt2-pwa-gae-se]
target service:  [default]
target version:  [20181021t022312]
target url:      [https://nuxt2-pwa-gae-se.appspot.com]


Do you want to continue (Y/n)?  Y

Beginning deployment of service [default]...
╔════════════════════════════════════════════════════════════╗
╠═ Uploading 62 files to Google Cloud Storage               ═╣
╚════════════════════════════════════════════════════════════╝
File upload done.
Updating service [default]...done.
Setting traffic split for service [default]...done.
Deployed service [default] to [https://nuxt2-pwa-gae-se.appspot.com]

You can stream logs from the command line by running:
  $ gcloud app logs tail -s default

To view your application in the web browser run:
  $ gcloud app browse

デプロイが完了したら、表示されているURLにアクセスしてみましょう。
デモページ: https://nuxt2-pwa-gae-se.appspot.com

GAE/SEでNuxt.jsのPWAが動作しました!! LighthouseでもPWAに対応していることが確認できます。

f:id:uwai-shinnosuke:20181021054852p:plain
f:id:uwai-shinnosuke:20181029013520p:plain

そのうえ、HTTP/2で通信されています。

f:id:uwai-shinnosuke:20181029100841p:plain

まとめ

GAE/SE Node.jsでNuxt.js v2のPWAを動作させました。
モダンなWeb技術をごく簡単にデプロイでき、運用の手間もかからない。素晴らしいアーキテクチャだと思いませんか? また、GAE/SEがすぐさまNode.js 10に対応したところを見ると、今後のアップデートにも期待が持てますね。

GCPにはその他にも魅力的なサービスが揃っています。例えば、通知サービスのFirebase Cloud Messagingを組み合わせると、PWAをより活かすことができるでしょう。

ぜひお試しください!

採用情報

DMM.com エンターテインメント本部では、エンジニアメンバーを募集しております。興味のある方はぜひ下記募集ページをご確認下さい!

dmm-corp.com