Docker
docker-compose
nuxt.js

SSR する時にフロントエンドからバックエンドのエンジニアに最小限伝えておきたい Nuxt.js の挙動


というわけで

実際に手を動かしてもらった方が早いので Tutorial を用意しました。


準備

vue-cli がなければ install しておく

% npm install -g vue-cli


デモプロジェクトを用意する

mkdir practice-nuxt-docker && cd $_

vue init nuxt-community/starter-template myapp
touch myapp/Dockerfile docker-compose.yml
cd myapp
yarn init -y
yarn
yarn nuxt build # 重要
cd ..

create myapp/Dockerfile

RUN yarn add nuxt

WORKDIR /app
ENV HOST 0.0.0.0
EXPOSE 3000
CMD yarn nuxt start

create Dockerfile

version: '3' 

services:
nuxt:
build: myapp
tty: true
volumes:
- "./myapp:/app"
ports:
- "3000:3000"

docker-compose build

docker-compose up

http://localhost:3000 へアクセスして動作確認します。nuxt build が必要ですのでデプロイツールを構築する人にちゃんと伝えておきましょう。


最初の Request は Node.js が HTML を返しますが、その直後にブラウザ側でレンダリングされている

以下のように修正します。

% git diff                                                               [master ~/samurai/practice-nuxt-docker/myapp]

diff --git a/myapp/pages/index.vue b/myapp/pages/index.vue
index cf60434..2e2e2b5 100644
--- a/myapp/pages/index.vue
+++ b/myapp/pages/index.vue
@@ -8,6 +8,8 @@
<h2 class="subtitle">
Nuxt.js project
</h2>
+ <h3 v-if="isClient()">Client</h3>
+ <h3 v-else>Server</h3>
<div class="links">
<a
href="https://nuxtjs.org/"
@@ -28,6 +30,11 @@ import AppLogo from '~/components/AppLogo.vue'
export default {
components: {
AppLogo
+ },
+ methods: {
+ isClient() {
+ return (typeof window !== 'undefined' && window.document)
+ }
}
}

Nuxt.js のアプリを起動します。(以下の例は docker を経由しない例です)

cd myapp

yarn nuxt build
yarn nuxt start

この状態で http://localhost:3000 を Reload すると一瞬 Server と表示されますが、直後に Client に置き換わる挙動を確認する事ができます。これ以降はブラウザ側で JavaScript が実行されます。

最初のリクエストの段階では Node.js が Javascript を処理するため、このタイミングでは localStorage へのアクセスはできません。access_token の保持などは cookie を使うと良いでしょう。素直に @nuxtjs/axios を使っていれば Header の情報はそのままバックエンドに渡されるはずです。たぶん。

https://axios.nuxtjs.org/options#proxyheaders


nuxt build が生成しているファイルの行方

nuxt build を実行すると以下のように圧縮された js ファイルが生成されます。

Hash: 893dbac70d5b02802558

Version: webpack 4.22.0
Time: 6225ms
Built at: 2018-10-23 12:02:07
Asset Size Chunks Chunk Names
41297c5455157e9ca810.js 2.18 KiB 3 [emitted] runtime
714f38d18aae84388e71.js 3.84 KiB 2 [emitted] pages/index
LICENSES 422 bytes [emitted]
a136c91fe4110122940e.js 31.1 KiB 0 [emitted] app
ca15e729f68a6493bcb3.js 134 KiB 1 [emitted] commons.app

これは以下の場所に配備されます。


% ls -l myapp/.nuxt/dist/client
total 360
-rw-r--r-- 1 okamuuu staff 2228 10 23 12:02 41297c5455157e9ca810.js
-rw-r--r-- 1 okamuuu staff 3928 10 23 12:02 714f38d18aae84388e71.js
-rw-r--r-- 1 okamuuu staff 423 10 23 12:02 LICENSES
-rw-r--r-- 1 okamuuu staff 31824 10 23 12:02 a136c91fe4110122940e.js
-rw-r--r-- 1 okamuuu staff 137438 10 23 12:02 ca15e729f68a6493bcb3.js

そして以下の URL で配信されます。このファイルを nuxt start で動いているプロセスが返すのは効率が悪い気がするのでどうやってデプロイしてキャッシュしたりするかなどをインフラエンジニアに相談してみてください。

http://localhost:3000/_nuxt/41297c5455157e9ca810.js

http://localhost:3000/_nuxt/714f38d18aae84388e71.js
http://localhost:3000/_nuxt/LICENSES
http://localhost:3000/_nuxt/a136c91fe4110122940e.js
http://localhost:3000/_nuxt/ca15e729f68a6493bcb3.js


asyncData

非同期処理を SSR する場合、Nuxt.js では asyncData に記述するだけで簡単に実装できます。


mkdir myapp/pages/posts
touch myapp/pages/posts/index.vue

create pages/posts/index.vue


<template>
<div class="container">
<h1>Posts</h1>
<ul>
<li v-for="(post, index) in posts" :key="index">
<nuxt-link :to="{ name: 'posts-id', params: { id: post.id } }">{{ post.title }}</nuxt-link>
</li>
</ul>
<p><nuxt-link to="/">Back to home page</nuxt-link></p>
</div>
</template>

<script>
import axios from 'axios'

export default {
asyncData({ req, params }) {
return axios.get('https://jsonplaceholder.typicode.com/posts')
.then((res) => {
return { posts: res.data.slice(0, 5) }
})
}
}
</script>

nuxt あるいは nuxt build && nuxt start でプロセスを起動してから以下の curl コマンドを実行してください。非同期処理で取得している情報が HTML で記述されてる事を確認できます。

curl -s http://localhost:3000/posts


他にも何かあれば

追記します。


  • 非同期処理であっても Node.js のプロセスで rendering して HTML を返す挙動が若干イメージしづらいようなので asyncDate について を追記しました。