Nuxt.js v2でSPA×SSR×PWAのWebアプリをGAE/SE Node.jsでサーバーレスでリリースする

#はじめに
自分のポートフォリオサイトを作りたいなーと考えていたのですが、せっかくなので最近話題のNuxt.jsを使って作ろうと思ったのがきっかけです。クラウドにGCPを使用したのは、肌感でAWSの方が使われてるなぁと感じたのと、触ったことのないGCPを触ってみたいという思いからです。

今回の制作にあたってDMMの上井さんが書いた記事が非常に参考になるので是非御覧ください。
https://inside.dmm.com/entry/2018/11/06/nuxt2-pwa-gae-se

また、この記事は上記のものをベースに自分がハマったところや、バージョンの変更で変わったところなどを書き加えたものになります。
#Nuxt.jsの解説
[Nuxt公式ドキュメント](https://ja.nuxtjs.org/guide/)によると
>サーバサイドとクライアントサイドのディストリビューションを抽象化しつつ UI レンダリングすることに焦点を当てています。

>Nuxt.js はサーバーサイドレンダリングをもっと楽しめるように Vue.js アプリケーションの開発に必要な設定をあらかじめプリセットしています。


>さらに、Nuxt.js はシングルページアプリケーション（spa モード）を手早く作成する際にも使えます。バックオフィスのアプリケーションとして動きつつ、Nuxt の機能を使うのに便利です。

簡潔に言うと、Vue.jsにSSRやPWAなどに必要な設定があらかじめセットされたのもになります。
また、Nuxt.jsは規約があるのでVueは自由すぎるといった人にもオススメです。

##SPA(Single Page Application)
SPAはその名の通り、単一のページでコンテンツの切り替えを行うWeb アプリケーションのアーキテクチャの名称です。
ブラウザによるページ遷移を行わずにコンテンツの切り替えなどを行うことで、リアクションが非常に早く、UXを大きく向上させることができます。また、ネイティブアプリと同様の挙動が出来ます。

##SSR(Server Side Rendering)
通常のレンダリングは、下図の左にあるブラウザからサーバへリクエストを出し、それがサーバへ到達すると、その裏にデータベースなどがあって、データベースから値を取ってきてコンポーネントやテンプレートエンジンなどを使ってHTMLにしてブラウザへ返す、という動きをしています。
![レンダリング.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/398489/14cf557d-08fb-13a0-e20f-10b0f8607960.png)

それが最近は、ブラウザの中にも、これまでサーバ側で動いていたHTMLを構築するための仕組みが入ってきています。

Serevr Side Renderingというときに、いろんな意味があるのですが、狭義の意味としては、HTMLを構築する仕組みがブラウザとサーバで共通のものが入ってきていて、それを共通のロジックを使って動かしましょう、というのがServer Side Renderingです。
![レンダリング2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/398489/169d54f4-bd2d-f855-48f5-3ca564fb561c.png)



前述したSPAは、SEOが弱かったり、ファーストビューのローディングが長い欠点がありますが、SSRを使えばそれらを改善することが出来ます。一般的に実装コストは高いと言われるSSRですが、Nuxt.jsであれば手軽にSSRが実現できます。
##PWA(Progressive Web Apps)
PWAは、モバイル向けWebサイトをネイティブアプリのように使える仕組みです。
PWAに対応することで

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

などといったメリットが生まれます。

Nuxt.jsにはPWAモジュールが用意されているので、簡単にPWAを実装することが出来ます。
#GAE/SE Node.js
[Google App Engine公式ドキュメント](https://cloud.google.com/appengine/docs/whatisgoogleappengine?hl=ja)によると
>Google App Engine は、Google のインフラの上でアプリケーションを作り、実行できるようにする PaaS （ Platform as a Service : サービスとしてのプラットフォーム）です。App Engine アプリケーションは、作るのもメンテナンスするのも簡単で、トラフィックやデータストレージのニーズの増減にあわせてスケーリングするのも簡単です。App Engine を使えば、自分でメンテナンスしなければならないサーバーはなくなります。単純にアプリケーションをアップロードすれば、それだけで実行できます。

特徴をまとめると
###1.サーバーレス
サーバーレスにすることで、運用や見積もりをする必要がなくなります。
###2.高速・柔軟なスケール
トラフィックに応じて瞬間的にスケーリングするため、急なトラフィック増にも問題なく対応できます。また、実際に使用したリソース量に基づいて従量的に課金されるため無駄がありません。
###3.CDN配信
CDN配信によって、GAEインスタンスに負荷をかけることなく、ユーザーに速くコンテンツを提供できます。
###4.HTTPS & HTTP/2
HTTPSの対応も非常に簡単で、HTTP/2に関してはデフォルトで対応しています。
###5.東京リージョン提供
東京リージョン（asia-northeast1）で提供されているため、国内向けのWebサービスでも安心して利用できます。
###6.低価格
一般的に、サーバーを持つよりも費用が安く抑えられます。
また、新規登録で12か月間 $300分の無料トライアルが利用できます。
#実装方法
解説が長くなりましたが、これから実装方法を解説していきます。

また、GitHubにソースコードを上げてありますのでよろしければ参考にしてください（若干の差異あり）。
https://github.com/Shinnopo/nuxt2-pwa-gae

##開発環境
今回は、
Node.jsはGAE/SEに合わせてNode.js 10を使用しています。ここで、Node.js 10を
以外のバージョンを使ってしまうとGAE/SEへのデプロイがうまくいきません。

```
$ node -v
v10.15.3
$ npm -v (Yarnでも可)
6.10.3
```

##Nuxt.js v2プロジェクト作成
Nuxt.js v2からcreate-nuxt-appが導入され非常に簡単にプロジェクトを作成できるようになりました。

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

# Yarnを利用する場合
$ yarn create nuxt-app プロジェクト名
```
この際、インタラクティブ形式で以下の設定をします。

```
? Project name nuxt2-pwa-gae
? Project description Nuxt.js v2 PWA on GAE/SE
? Author name Shinnopo
? Choose the package manager Npm
? Choose UI framework None
? Choose custom server framework None (Recommended)
? Choose Nuxt.js modules (Press <space> to select, <a> to toggle all, <i> to inv
ert selection)
? Choose linting tools (Press <space> to select, <a> to toggle all, <i> to inver
t selection)
? Choose test framework None
? Choose rendering mode Universal (SSR)
```

**Project name: プロジェクト名**
適宜入力してください。

**Project description: プロジェクト概要**
適宜入力してください。

**Choose a package manager: パッケージマネージャの選択**
npm or Yarnの選択です。本記事ではnpmで解説を進めます。

- npm
- yarn

**Use a custom UI framework: UIフレームワークの選択**
次のうちから選択します。本記事では扱わないためnoneを選択しましたが、利用したいものがあれば選択してください。

- none
- bootstrap - Bootstrap
- vuetify - Vuetify
- bulma - Bulma
- tailwind - Tailwind CSS
- element-ui - Element
- buefy - Buefy

**Use a custom server framework: サーバーフレームワークの選択**
次のうちから選択します。今回はNuxt.jsのデフォルトサーバーを利用するためnoneを選択します。

none
express - Express
koa - Koa
adonis - AdonisJs
hapi - hapi.js
feathers - Feathers
micro - Micro
サーバーのカスタマイズを求める方は、お好みのフレームワークを選択してください。



**Choose Nuxt.js modules: モジュールの利用**
Nuxt.js用モジュールの選択です。今回はPWAにするので、PWAを選択します。

- Axios
- Progressive Web App (PWA) Support

**Choose linting tools: 構文チェックツールの利用**
構文チェックツールです。お好みのを選択してください。

- ESLint
- Prettier
- Lint staged files

**Choose test framework: テスト用フレームワークの選択**
テスト用フレームワークの選択です。今回はnoneにしましたが、利用したいものがあれば選択してください。

- None 
- Jest 
- AVA 


**Choose rendering mode: レンダリングモードの選択**
SSR or SPAを選択します。SSRするには必ずUniversalを選択してください。

- Universal
- Single Page App

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

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

```
$ npm run dev
```

(http://localhost:3000/) にアクセスして、以下のようなページが表示されたら成功。

![スクリーンショット 2019-08-17 1.53.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/398489/8f633a73-73f7-6860-c173-2c3f0422edaf.png)

なんとたったこれだけで、Nuxt.jsのプロジェクトが作成できちゃいます。

##PWA化
初期設定でPWAを選択していますが、これだけではPWAに対応していません。

まず以下のコマンドでインストールをします。

```
$ npm install --save @nuxtjs/pwa
```
そして、`Nuxt.config.js`にPWA用の記述をします。

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

`.gitignore`に`sw.*`を追加します。これはビルド時に生成されるService Worker系のファイルです。
(初期設定でPWAを選択していれば、これはすでに書き込まれていると思います。)

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

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

```
# ビルド
$ npm run build

# サーバー起動
$ npm start
```

PWA化が完了しService Workerが動作しているのが確認できます。
![スクリーンショット 2019-08-17 1.59.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/398489/da9c5c6e-b67c-aa78-2330-0c86c6851046.png)

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

##GAE/SEのセットアップ
詳しいことは、[公式ドキュメント](https://cloud.google.com/appengine/docs/standard/nodejs/quickstart)に載ってます。

###GCPプロジェクト作成
GCPの[コンソール](https://console.cloud.google.com)から新しいプロジェクトを作成します。
![スクリーンショット 2019-08-17 2.11.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/398489/7a5f945b-4687-9e70-509c-450a70d8d839.png)
プロジェクトIDは後ほど使用します。

###GAE有効化
プロジェクトの作成が完了したら、左サイドバーの[App Engine]よりダッシュボードに移り、アプリケーションを作成を選択します。

次に、リージョンの選択画面に移動します。GAEのリージョンは後から変更できないので、慎重に選択してください。東京リージョンはasia-northeast1です。
![スクリーンショット 2019-08-17 2.15.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/398489/7bc31e37-8569-3514-a62d-f8923eb65d3d.png)

アプリを作成を選択したら、次に言語を選択します。
今回はNode.jsを使用するので、プルダウンからNode.jsを選択します。
![スクリーンショット 2019-08-17 2.17.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/398489/ca57eb38-a6a4-4edf-6610-eb5b0e445b27.png)
Environmentは標準を選択します。

次へを選択すると、GAEの準備が完了します。

###Google Cloud SDKのセットアップ
上の手順を行っていれば、以下の画面に移動していると思います。
Google Cloud SDK によるデプロイからCloud SDKのダウンロードを選択します。
![スクリーンショット 2019-08-17 2.20.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/398489/81a3e229-dfaa-343b-23b5-b5da26ed4873.png)
セットアップが完了したら、Google Cloud SDKに先ほど作成したプロジェクトを設定します。

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

ここも詳しくは[公式ドキュメント](https://cloud.google.com/sdk/docs/#install_the_latest_cloud_tools_version_cloudsdk_current_version)を参照してください。

##app.yaml設置
app.yamlはGAE/SEのサーバーの振る舞いを定義する必須ファイルで、ここに色々設定を書いていきます。 色々オプションがあるので、詳しくは[公式ドキュメント](https://cloud.google.com/appengine/docs/standard/nodejs/config/appref)を御覧ください。

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

```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'
```
上記のオプションについて解説していきます。

**run time**

```
runtime: nodejs10
```
ここに使用するNode.jsのバージョンを指定します。

**インスタンスクラス**

```
instance_class: F2
```
GAE/SEのデフォルトインスタンスクラスはF1ですが、その1つ上のF2を使用します。
Nuxt.jsアプリケーションの容量が大きくなると、メモリ不足で落ちるからです。F1のメモリは128MBしかありませんが、F2ならば256MBあるので十分に動作します。

※F2を使用すると、無料利用枠内で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配信されるということです。

**ホストとポート**

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

※package.jsonやnuxt.config.jsでhostやportを変更した人は、host0.0.0.0、port8080に合わせておいてください。これを合わせないとデプロイしてもアクセスが出来ません。

#デプロイ
いよいよデプロイです。

デプロイする前に1度Nuxt.jsのプロジェクトをbuildする必要があります。(ここでbuildし忘れるとServer Errorが起こります。）

```
$ npm run build
```

buildが完了したら、いよいよGAEにデプロイします。
GAEは以下のコマンドだけでデプロイが出来ます。

```
$ gcloud app deploy
```

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

```
Do you want to continue (Y/n)?  
```
と聞かれるので、`Y`を入力します。

```
Services to deploy:

descriptor:      [/Users/shinnopo/Documents/nuxt2-pwa-gae/nuxt-pwa-gae/app.yaml]
source:          [/Users/shinnopo/Documents/nuxt2-pwa-gae/nuxt-pwa-gae]
target project:  [nuxt2-pwa-gae-250017]
target service:  [default]
target version:  [20190817t025042]
target url:      [https://nuxt2-pwa-gae-250017.appspot.com]


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

Beginning deployment of service [default]...
Created .gcloudignore file. See `gcloud topic gcloudignore` for details.
╔════════════════════════════════════════════════════════════╗
╠═ Uploading 37 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-250017.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にアクセスするか、`$ gcloud app browse`を実行します。
[デモページ](https://nuxt2-pwa-gae-250017.appspot.com/)

ページが表示されればデプロイ成功です！

これで、GAE/SEでNuxt.jsのPWAが動作しました！！　Lighthouseでも確認してみると、PWAに対応していますね。
![スクリーンショット 2019-08-17 13.39.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/398489/3aa98913-4024-f4d0-086d-a8cff1a4f286.png)
![スクリーンショット 2019-08-17 13.40.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/398489/eb11f8ea-382e-96a7-b7cf-b0af9bed031f.png)

#まとめ
GAE/SE Node.jsでNuxt.js v2のPWAを動作させました。
最近話題のNuxt.js思った以上に便利な機能が盛りだくさんで、しかもそれらを簡単に扱えることにとても魅力を感じました。SPAやSSR、PWAなど話題の技術を簡単に実装できるのでサクッと作ってパパっとリリースしたいときには、もってこいだと思います。　

GCPも簡単にデプロイができ、様々な機能が盛りだくさんなので、AWSだけではなく今後はGCPも更に広まっていってほしいです。

GCPには、通知サービスのFirebase Cloud Messagingがあり、これを使用することでPWAをより活かすことができると思います。

Nuxt.js×GAE/SEめちゃめちゃ便利なので皆さんも是非お使いになってみてください！！

また、今回の技術を使って制作したポートフォリオサイトもあるので、そちらも是非ご覧になってください。
https://shinnopo.me/

#参考文献
[Nuxt.js v2とGAE/SE Node.jsでSPA×SSR×PWA×サーバーレスを実現する](https://inside.dmm.com/entry/2018/11/06/nuxt2-pwa-gae-se)

[Server Side Renderingについて知るべきこと。Server Side Renderingとは何か？ それによって何が改善されるのか？（前編） ng-japan 2017](https://www.publickey1.jp/blog/17/server_side_renderingserver_side_rendering_ng-japan_2017.html)

[PWAとは（Progressive Web Appsとは）](https://www.seohacks.net/basic/terms/pwa/)