Nuxt + Express + Nginx with Docker 作成
目的
Nginx, Nuxt, Express を用いたDocker環境を作成した。 Nginxを用いる理由は、アクセスログをnginxにやらせたかったからで必須ではないです。 envoy?的なイメージ。
設定関連
Nginx
unix socketでExpressと通信をするようにする。 ポートでも問題ないけど、試して見たかったのでunix socket を利用してみた。
nginx.confこんな感じ。
upstream nuxt_app { server unix:/app/tmp/nuxt.sock; } server { listen 80; server_name ${SERVER_NAME}; access_log stdout; error_log stderr warn; access_log /app/log/access.log tsv; error_log /app/log/error.log warn; location / { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_pass http://nuxt_app; } }
Nuxt & Express
app/tmp をvolume マウントして、Nginxから見られるようにすることで、unix socketによる通信を可能にしてます。 DockerFileはこんな感じ。
FROM node:11.8.0-alpine ENV LANG C.UTF-8 RUN mkdir /app WORKDIR /app ADD . /app VOLUME /app/tmp RUN npm i -g yarn RUN yarn install --production ARG ENV RUN yarn nuxt build CMD node server/index.js
チュートリアル通り、nuxtをセットアップして、server.js をunix port でlistenするようにする。 EC2だと、pm2使ってたが、dockerだとなくていいらしいので楽!
参考
Node.jsをunix domain socketでlistenしたときにNginxからプロキシできない - なっく日報
async function start() { ...省略 app.use(nuxt.render); // Listen the server const socketPath = '/app/tmp/nuxt.sock'; // 必要ならここで既にsockファイルが存在する場合削除が必要かも。 app.listen(socketPath, (err) => { if (err) throw err; fs.chmodSync(socketPath, 660); consola.ready({ message: `Listening on socket`, badge: true }); } } start();
docker-compose
こんな感じ。
version: '2' services: app: build: context: . args: ENV: ${ENV} env_file: - .env nginx: build: nginx ports: - "80:80" volumes_from: - app env_file: - .env depends_on: - app
Nuxt & Docker 開発環境構築
目的
Nuxt(SSR)をDockerで開発をスムーズにできるように、雛形作ったのでメモ
レポジトリ
https://github.com/okadak/nuxt_buildergithub.com
説明
READMEに書いたけど、いつか消すかもなので、こちらにもメモ
初期手順
terminalから、以下を実行すると、dockerにnodeをインストールした環境ができる。
$ docker-compose -f docker-compose.init.yml up -d # 「builder_nuxt_1」は、container nameで、dokcer ps で確認できるので、自分の環境と合わせて。 $ docker exec -it builder_nuxt_1 sh
これで、dockerの中に入れるので、dockerの内部で、nuxtをinstall
$ yarn create nuxt-app . # こんな感じに回答する。 > Generating Nuxt.js project in /app ? Project name app ? Project description My mind-blowing Nuxt.js project ? Use a custom server framework express ? Choose features to install Linter / Formatter, Prettier, Axios ? Use a custom UI framework none ? Use a custom test framework none ? Choose rendering mode Universal ? Author name ? Choose a package manager yarn # install終わったら、installしたものが、localにコピーされてるはず。 # 一回dockerから出る。 $ exit
実際の開発をするためのコンテナを起動する。
$ docker-compose up -d --build
これで、localhost:3000
で、nuxtにアクセスできる。
開発時
毎回、コマンド打つの面倒だから、makeコマンドを利用する。
# こんな感じで起動できる。 $ make start # バグったらこれ試してみる $ make rm_node_modules $ make restart
注意: 初期手順後すぐに make コマンドを利用する時は、一回下のコマンドで、立ち上がってるdockerを殺す。
$ docker-compose kill $ docker-compose rm -f
よく使うpackage
- 全ページで利用するCSS変数を設定する
- Google Analyticsの設定
- pugとか、scss使いたい
- https://ja.nuxtjs.org/faq/pre-processors/
yarn add pug@2.0.3 pug-plain-loader node-sass sass-loader
- 画像の遅延読み込みをしたい
- API叩きたい
エディターについて
Vue開発においては、vscodeがおすすめ!
{ "eslint.validate": [ "javascript", "javascriptreact", { "language": "vue", "autoFix": true } ], "editor.formatOnSave": true, "prettier.eslintIntegration": true, "eslint.autoFixOnSave": true }
{ "recommendations": [ "octref.vetur", "esbenp.prettier-vscode", "eamodio.gitlens" ] }
これをrootディレクトリに置くと、component へコードジャンプできる!
{ "include": ["./components/**/*"], "compilerOptions": { "module": "es2015", "baseUrl": ".", "paths": { "~/components/*": ["components/*"] } } }
Nuxt 2.4.0 へ移行した
Nuxt 2.4.0 がリリースされたので、会社のWEBサイトで、本番に反映してみた。 ja.nuxtjs.org
やったこと
storeをモジュールモードへ
https://ja.nuxtjs.org/guide/vuex-store/ クラシックモード使ってると、警告が出たので、モジュールモードに変更。
プラグインのファイル名変更
plugins: [ { src: '~/plugins/plugin.js', ssr: false } ]
を、
plugins: [ '~/plugins/plugin.client.js' ]
に変更。 ファイル名だけで、SSRの時、読み込まないようにするとかが、分かりやすくなった。
yarn.lock削除して、yarn install
念のため一旦削除
エラったこと
NODE_ENV=production yarn run build
時だけ、file-loader がなくてエラーが起きた。
単に、yarn add file-loader
後に、buildしたらできたけど、前までは必要なかったから原因よくわからないまま。
感想
さらなる速度改善によるUXの改善が見込めそう。 まだ、5時間ぐらいしか本番運用して、経過してないけど、安定して動いてくれてて、毎回コミュニティの方々には感謝しかない。 NuxtとVueのコミュニティの方々に、寄付しないといけないなと思った。
あと、そろそろNuxt Usersに感謝の気持ちを書かないと。 Nuxt Users 💻 · Issue #4681 · nuxt/nuxt.js · GitHub
DockerFileメモ
メモ内容
開発時は、Nuxt + Railsで、本番はnuxt build したものを、RailsのPublicフォルダに渡すみたいなことをする時のDockerFileメモ。 実際使うときは、Nuxtビルドしたものは、S3とかに置くと思うので、使い道はないかもしれないけど...
開発時
Nuxt
FROM node:10.13.0-alpine ENV LANG C.UTF-8 # Create app directory RUN mkdir /frontend WORKDIR /frontend ADD . /frontend RUN npm i -g yarn ADD yarn.lock /frontend/yarn.lock RUN yarn install CMD yarn run dev
Rails
FROM ruby:2.5.3-alpine3.8 RUN cd /tmp \ && apk --no-cache add \ curl \ curl-dev \ libstdc++ \ libxml2-dev \ libxslt-dev \ linux-headers \ mysql-client \ mysql-dev \ postgresql-client \ postgresql-dev \ pcre \ ruby-dev \ ruby-json \ tzdata \ yaml \ yaml-dev \ bash \ build-base \ zlib-dev # Rails App RUN mkdir /app WORKDIR /app ADD Gemfile /app/Gemfile ADD Gemfile.lock /app/Gemfile.lock RUN bundle install --jobs=4 ADD . /app RUN mkdir -p tmp/sockets RUN mkdir -p tmp/pids # Expose volumes to frontend VOLUME /app/public VOLUME /app/tmp VOLUME /app/log # Start Server CMD bundle exec puma -e local
DockerCompose
version: "2" services: app: build: . image: rails volumes: - .:/app ports: - "3000:3000" nuxt: image: nuxt build: frontend ports: - "4000:4000" volumes: - .:/frontend - node-modules-data:/frontend/node_modules volumes: node-modules-data:
本番
# build FROM node:10.13.0-alpine AS build-html # node-sassのためにpython2が必要 RUN apk --no-cache add python RUN mkdir /fontend ADD frontend /frontend WORKDIR /frontend RUN npm i -g yarn RUN yarn install ARG ENV RUN yarn run build:production # RUN FROM ruby:2.5.3-alpine3.8 RUN cd /tmp \ && apk --no-cache add \ curl \ curl-dev \ libstdc++ \ libxml2-dev \ libxslt-dev \ linux-headers \ mysql-client \ mysql-dev \ postgresql-client \ postgresql-dev \ pcre \ ruby-dev \ ruby-json \ tzdata \ yaml \ yaml-dev \ bash \ build-base \ zlib-dev RUN mkdir /app WORKDIR /app ADD Gemfile /app/Gemfile ADD Gemfile.lock /app/Gemfile.lock RUN bundle install --jobs=4 ADD . /app RUN mkdir -p tmp/sockets RUN mkdir -p tmp/pids # copy build html file COPY --from=build-html /frontend/public /app/public # Expose volumes to frontend VOLUME /app/public VOLUME /app/tmp VOLUME /app/log # Start Server ARG ENV CMD bundle exec puma
TwitterAPIで、DMを受け取り、DMを返す
目的
お手伝いしてる案件で、TwitterのAPI使ってみたのでメモ APIはRailsで実装した。
方法
Twitterの開発用アカウント作って、自分のアプリケーションのURLを登録する。
下のURLがわかりやすいので、参考に。 qiita.com
この通りにやれば、別のTwitterアカウントから、開発しているTwitterアカウントにDirectMessageを送ると、APIが叩かれる。
APIの中身を実装する。
下はRubyで実装してあるので、この通りにすればとりあえず動いた GitHub - twitterdev/SnowBotDev: A demo illustrating the Twitter Account Activity and Direct Message APIs.
quick_reply っていう項目があって、これにデータを入れて、レスポンスを返すと、DMの下に選択肢が出るので、便利だなと思った。
簡単に説明
注) パラメーターはミスしてるかもなので、実際に実装するときは、SnowBotDevのgithub参考にしてください。
DMが送られると、TwitterからAPIに、以下のパラメータでリクエストが届く。
params = { direct_message_events: { type: 'message_create', sender_id: 'DMを送信したユーザーのID' message_create: { message_data: { text: 'hogehoge' // ここに文字が入ってるときは、直接DMが送られたとき。 quick_reply_response: { metadata: 'hogehoge' // ここに文字が入ってるときは、こちらが提示した選択肢を押したとき } } } } }
このリクエストをみて、下のようなレスポンスを作成する。
{ event: { type: 'message_create', target: { recipient_id: 'DMを送りたいユーザーのID' } message_create: { message_data: { text: 'DM message' // DMで送りたいメッセージを入れる quick_reply: { // ここにデータを入れると選択肢を提示できる。 options: { label: '選択肢に出てくるラベル', description: '選択肢の説明', metadata: '選択肢のIDみたいなもの。この選択肢を押したとき、quick_reply_responceにこの値が入って、再度イベントが送られる。' } } } } } }
このレスポンスを、OAuth認証利用して、TwitterのAPIに送る
https://github.com/twitterdev/SnowBotDev/blob/master/app/helpers/api_oauth_request.rb
Nuxt&SSRでsetIntervalを使う時の注意点
上記にあるように beforeCreate
と created
でsetInerval
を使うと、SSR中に呼び出されるので、サーバでsetIntervalが実行されて、clearIntervalをしない限り永遠に続いて行く...
気づかず実装してしまって、焦ったのでメモ。
怖いので、mixins 使ってwrapperしてみた。 考えたこととしては、 - created でなく、mounted を使う - process.server 時に行わないようにする。(mountedの時点で呼ばれないはずだからいらないかも?) - beforeDestory の時あたりで、clearIntervalを行う - 本当は必要ないが、未来の自分が信用できないので、念のため時間設定でclearIntervalを設定しておく。
以下プログラム
export default { data() { return { intervalID: null }; }, methods: { setTimer: function(callback, intervalTime) { if (process.server) return; this.intervalID = setInterval(() => { callback(); }, intervalTime); // 万が一の不具合を防ぐために、ClearIntervalを実施する。(とりあえず30分) setTimeout(() => { clearInterval(this.intervalID); }, 30 * 60 * 1000); } }, beforeDestroy() { clearInterval(this.intervalID); } };
<script> import timer from '~/mixins/utils/timer.js' export default { mixins: [timer], mounted: { this.setTimer(this.function, intervalTime); } } </script>
NuxtにLazyLoadを入れてみた
2019/3/5 追記
LazyLoadを導入しましたが、サイト速度が向上したかに関して、現状測定していません。 Page Speed Insight の点数が向上したという結果のみ得ています。 そのため、実際にパフォーマンス向上に貢献できているかは不明です。
Webパフォーマンスの振り返り 2018 - Webパフォーマンスについて
上記のtakehora様の記事にあるように、<img decoding ="async">
を用いる方がいいのではないかと思います。
(ただ、そこに関しても自身で測定できてないので、全体のパフォーマンス測定したら追記します。)
経緯
画像がいっぱい表示されるサイトをNuxt&SSRで、作っている。 なので、画像を読み込む時間が結構かかって困ってた。 LazyLoadすればいいことはわかってたけど、SEOへの影響がよくわからなくて、プロダクト的にNGだった...
しかし、Googleが11月にこのような実装方法を出してくれたので、Goサインがでた! developers.google.com
やったこと
まず、VueLazyLoadがめっちゃ便利なので、それをインストール
import Vue from 'vue'; import VueLazyload from 'vue-lazyload'; Vue.use(VueLazyload, { preLoad: 1.1, attempt: 1, observer: true, observerOptions: { rootMargin: '0px', threshold: 0.1 } });
上記のように、pluginファイル作って、nuxt.config.js に ssr: false
にして、追記
observerをtrueにすることが必要で、そうすると、Googleの解説記事にある ブラウザnativeの IntersectionObserver を使ってくれる。
ただ、これだけだとIEとか動かないので、app.htmlに、直接polyfillを追加した。
<script async src="https://cdn.polyfill.io/v2/polyfill.min.js?features=IntersectionObserver" ></script>
困ったこと
初期表示時は、SSRしたもの画像が表示されるので、vue-lazyloadのマニュアル通りに、初期画像を設定しても上手くいかなかった。 なので、下のようなコンポーネント作って、Lazyload使うときは、常にこれ使うようにしたらいい感じになった!
<template lang="pug"> img(src="~assets/images/background/pixel.png" :alt="alt" v-lazy="src") </template> <script> export default { props: { src: { type: String, required: true }, alt: { type: String, required: true } } }; </script>
LazyLoadめっちゃ便利!
追記
下記の記事を見つけた。
画像の非同期デコーディング
Webパフォーマンスの振り返り 2018 - Webパフォーマンスについて
<img decoding ="async">
とつけると、画像デコーディングが非同期で行われ、ページ表示まで早くなるらしい。
自分の環境(通信速度が早い)だと、LazyLoadした時と比べて、初期表示とかの体感では変わらなかった。
むしろ、LazyLoadしてると、表示後画像が出るまでにどうしても遅れる(特に画像が大きい時)ので、しない方が体感としては良い。
一方、audit(lighthouse)で試してみたら、点数がLazyLoadと比較して、明らかに下がっていた。
ということは、page speed insight でも点数は下がるはずで、これがSEOとかに影響与えないか(多分ないはずだけど)不安。
ただ、ユーザ体験を考えると、LazyLoadないほうがいいのかもと思った。
あとは、コストの問題だなって感じ。
大規模にならないとあんまり変わんないけど。
将来は、LazyLoadがブラウザ標準になるっぽいから、<img decoding='async' lazyload='on'>
という書き方がデフォルトになっていくのかも。