Vueで書かれた自社サイトをNuxt対応した時にAWSインフラでハマったポイントとその解決策

先週、弊社が運営する3Dモデル検索ウェブサービス「heymesh」をリニューアルしました!4/10にサービスをリリースしてから初の大きなリニューアルになります。

今回リニューアルした内容の一つに今までVue.jsで書かれていたheymeshをNuxtに対応させるという要件があったのですが、本記事はVueで書かれたサイトをNuxt対応させる途中でハマったポイントと、どのように解決したのかについて自分用のメモもかねてまとめました。

本題から少し外れるため詳細は割愛しますが、Nuxt対応するに至った経緯としてはOGPやSEO対策しやすくするという要求があったためです。

ハマりポイント1:S3ホスティングが使えない

heymeshの最初のリリースの際にはVue.jsで実装されていました。そのため、インフラ側はスタンダードにS3とCloudfrontでWebホスティングする手法を採用していました。

弊社ではインフラ担当(僕)がVue.jsのgitリポジトリにbuildspecを追加し、CodeBuildで連携してあげることでフロントエンジニアが簡単にVueプロジェクトをリリースできる環境を構築していました。

S3ホスティングはこのように手軽にデプロイフローが作れる点やサーバー管理しなくて良い点など嬉しいポイントがいっぱいでした。

今回もリニューアル開発開始当初は、Nuxt対応に関して僕の工数はないものとして計算していました。

しかし、フロントエンジニアからソースコードがあげられ、一旦開発環境でS3にデプロイを行った際に初めて異変に気づきました。

僕「あれ、index.htmlなくない?」

フロントエンジニア「あ、そうっすね。SSRなんで」

僕「ひいいいい」

問題の原因:フロントエンジニアとインフラエンジニアのお互いの理解不足

冷静に考えれば当たり前なんですよね(笑)サーバーサイドでレンダリングするためにNuxt対応してるんだからサーバー用意しろと

しかしフロントエンドとインフラのエンジニアが別々で稼働しており、気づくのが遅れてしまいました。

フロントエンジニアからすればVue.jsでの開発時と同様にnpmコマンドでローカルで動いているし、インフラがS3ホスティングで稼働しているというのが、SSRに対応することで問題になるというのが、インフラにある程度知識がないとなかなかイメージできないのではないかと思います。

またすっとこどっこいインフラエンジニア(僕)はSSRが具体的にどういった挙動になるのか、Vue.jsとの差分などを理解していなかったために、インフラを変える必要があることを認識するまでに時間がかかってしまっていました。

解決策:DockerとFargateに移行

今回発生した問題に対処するにあたり、まずNuxtをAWS内でデプロイしている事例をネット上で調べ、その中から我々の選択条件にあった手法を選択することにしました。

自分が調査した限りだと手法としては以下の三通りでした。

- Netlifyを使用する
- Lambda + API gatewayを利用する
- Docker化し、ECS (Fargate) を利用する

また我々の選択条件は以下のように列挙しました。

- できるだけAWS内で完結する形であること
- S3ホスティングのようにシンプルなデプロイであること
- できるだけ追加のコードを記述する必要がないこと

NetlifyはAWS外のサービスとなってしまうかつSSR対応はされてないため、二番目か三番目の方法を検討。二番目の場合、API GatewayをWebサーバーとして使ったり、LambdaとAPI Gatewayは何かとトラウマが多かったりするので、今回は三番目のDockerを使う方法を採用することにしました。

ちなみに二番の方法を取りたい方は以下のサイトが参考になります。

手順

まずDockerfileを追加してNuxtプロジェクトをDocker化します。

FROM node:9.11.1

ENV HOST 0.0.0.0

RUN mkdir -p /app
COPY . /app
WORKDIR /app

ARG ENV
ENV ENV $ENV

RUN cp /app/robots/$ENV.txt /app/static/robots.txt
RUN cp /app/sitemap.xml /app/static/sitemap.xml
RUN npm install && npm cache clean

RUN npm run build

EXPOSE 3000

CMD ["npm", "run", "start"]

環境によってrobots.txtを分けているので、ENVを引数にとり、指定された値によってコピーするrobots.txtを変えています。

Nuxtが本番環境用のサーバーを用意してくれているおかげでDockerfileのみ記述すればサーバーを起動できるというのは追加コードを書かなくて良いのでとても助かりました。

次にS3アップロードのために記述していたbuildspecをDocker化する処理内容に変更しました。

version: 0.2

phases:
  pre_build:
    commands:
      - REPOSITORY_URI=$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME
      - $(aws ecr get-login --region $AWS_DEFAULT_REGION --no-include-email)
  build:
    commands:
      - docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG --build-arg ENV="${ENV}" .
      - docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $REPOSITORY_URI:$IMAGE_TAG
  post_build:
    commands:
      - docker push $REPOSITORY_URI:$IMAGE_TAG
      - echo "[{\"name\":\"${CONTAINER_NAME}\",\"imageUri\":\"${REPOSITORY_URI}:${IMAGE_TAG}\"}]" > imagedefinitions.json
artifacts:
    files: imagedefinitions.json

buildしたコンテナをECRにプッシュして、imagedefinitions.jsonを使って、ECSのクラスターに稼働しているサービスを更新するようにしています。各環境変数は各自の環境に合うようにCodeBuild作成画面内で設定してください。

あとはCodePipelineでgithub(もしくはCodeCommit)と連携を行い、CodeDeployのデプロイ先をECSに設定すればgitでpushを行うたびに稼働中のNuxtプロジェクトを自動で更新することができます。

ECSではFargateでサービスを起動させてサーバー管理をほぼしない状態で運営をしています。

また従来通りCloudfrontを設定するため、NLBを設定します。ECSでサービスを作成する際にNLBをロードバランサーとして設定しましょう。

あとは今までS3をOriginとしていたCloudfrontのOriginを先ほど設定したNLBに向けることでブラウザ上からサイトをNuxtで実装されたウェブサイトを閲覧することが可能になります。

万が一、何かしらの設定をミスしてページが表示されない場合でも焦らずCloudfrontのOriginをS3に戻せばVueが表示されますので、Nuxt対応の移行が完全に完了するまではVue.jsがホスティングされているS3は保持しておくことをお勧めします。

今回のフロントの仕組みは図にすると以下のようになります。

今回発生した問題に関して、より短期間に開発チーム全体でデプロイを行い、統合テストを行うなどの開発フローに関する改善も行うことができました。

ハマりポイント2:Cloudfrontを通すと何故か404が返ってくる

NLBが発行するAレコードなどを直打ちでブラウザから確認するときちんとNuxtプロジェクトのウェブサイトが確認できるのに、CloudfrontのOriginをNLBに向けて確認すると下図のように404が返ってくることがあります。

僕も実はこのパターンで本番移行を行う際に何故かCloudfrontを通すと404になる事象に悩まされ、何度もOriginをS3に戻す作業を行なっていました。

問題の原因:Cloudfrontのデフォルトルートに「index.html」が設定されている

VueをS3ホスティングして公開していた際にCloudfrontの設定でデフォルトルートをindex.htmlに設定していました。しかしNuxt対応するとビルドしてもindex.htmlは生成されません。その結果、ブラウザは存在しないindex.htmlを見に行ってしまうので、404が返ってきてしまうのです。

解決策:Cloudfrontの設定修正

Cloudfrontの設定を修正します。

その際、修正が必要な箇所は二箇所あります。

- GeneralのDefault Root Object
- OriginのOrigin Path

これら両方とも空欄にすることでCloudfrontがindex.htmlを見に行かなくなるので、きちんとウェブページを表示することができます。

以上、VueプロジェクトをNuxt対応する際にすっとこどっこいインフラエンジニア(僕)がハマったポイントとその解決策についてご紹介しました。

「こんなミス自分はしないよ(笑)」と思われる方も多いかもしれないですが、これからNuxt対応を行う方の参考になれば幸いです。

またフロントエンジニアの人は一言インフラの方に「S3ホスティングじゃダメですよ」って助言してあげるともしかしたら幸せになれるかもしれません。

We're hiring!

MESONでは一緒にプロダクトを開発してくれるエンジニア&デザイナーをいつでも募集しています。クローラーに興味がある方や、すっとこどっこいエンジニア(僕)に代わってインフラを構築してくれる方などご興味ある方はぜひTwitterのDMなどでご連絡ください!

追記

どうやらnow.shというPaaSサービスがあるらしくて、これもNuxtのデプロイと相性が良いようです。

公式サイトはこちら

参考ページ


この記事が気に入ったら、サポートをしてみませんか?気軽にクリエイターを支援できます。

39

#エンジニア 系記事まとめ

noteに投稿されたエンジニア系の記事のまとめ。コーディングTIPSよりは、考察や意見などを中心に。
2つのマガジンに含まれています
コメントを投稿するには、 ログイン または 会員登録 をする必要があります。