Help us understand the problem. What is going on with this article?

SSR モードの Nuxt.js を Firebase に Deploy する Tutorial

書いてある事

大きく3点に分けて説明します。

  • Firebase で SSR する為に必要な最低限の知識
  • Nuxt.js を Express のミドルウェアとして動作させる方法
  • Nuxt.js を Firebase にデプロイする時の注意点

なお、SSR ではなく SPA で十分な人だったり Firebase Functions 使わない人には合わない記事かもしれません。

Firebase で SSR する為に必要な最低限の知識

まずは Firebase functions のプロジェクトを用意します。

% mkdir ~/nuxt-ssr-firebase && cd $_
% firebase init functions

以下の設問が出てくるので各自でFirebase のプロジェクトを選択、もしくは作成してください

? Select a default Firebase project for this directory: 

プロジェクトを選ぶと以下の質問に回答します。とりあえず何の事を聞かれているのかまだ分からない状態の人は以下を真似してして下さい。

# A functions directory will be created in your project with a Node.js
# package pre-configured. Functions can be deployed with firebase deploy.

? What language would you like to use to write Cloud Functions? JavaScript
? Do you want to use ESLint to catch probable bugs and enforce style? (y/N) No
? Do you want to install dependencies with npm now? (Y/n) Yes

現時点ではディレクトリ構成は以下のようになっていると思います。package.jsonnode_moduels~/nuxt-ssr-firebase/functions に配置されています。

% tree -a -L 2 -I 'node_modules' -I '.git'                                            .
├── .firebaserc
├── .gitignore
├── firebase.json
└── functions
    ├── index.js
    ├── node_modules
    ├── package-lock.json
    └── package.json

functions/index.js を修正します。

-// exports.helloWorld = functions.https.onRequest((request, response) => {
-//  response.send("Hello from Firebase!");
-// });
+exports.helloWorld = functions.https.onRequest((request, response) => {
+ response.send("Hello from Firebase!");
+});

firebase serve を使って localhost で firebase functions の動作を確認します。

% firebase serve --only functions:helloWorld
functions: helloWorld: http://localhost:5000/nuxt-ssr-xxxx/us-central1/helloWorld

確認できたら firebase deploy で cloud functions にデプロイして動作確認します。

% firebase deploy --only functions:helloWorld

おそらく次のようなURL でアクセスできると思います。https://us-central1-nuxt-ssr-xxxx.cloudfunctions.net/helloWorld

さて、asyncawait といった構文を使いたいと思いますので functions/package.json に以下の修正を加えておくとよいでしょう。

 {
   "name": "functions",
   "description": "Cloud Functions for Firebase",
+  "engines": {
+    "node": "8"
+  },
   "scripts": {
     "serve": "firebase serve --only functions",
     "shell": "firebase functions:shell",

次に Firebase hosting をセットアップします。

% firebase init hosting
? What do you want to use as your public directory? public
? Configure as a single-page app (rewrite all urls to /index.html)? No

最初の設問で public と答えてある場合は functions/public というディレクトリが作成されていると思います。ちなみに public404.html と index.html は後で消します。

ls public
404.html index.html

次に firebase.json に以下の様な rewrites ルールを追加して下さい。

       "firebase.json",
       "**/.*",
       "**/node_modules/**"
+    ],
+    "rewrites": [
+      {   
+        "source": "**",
+        "function": "helloWorld"
+      }   
     ]
   }
 }

以下のコマンドを実行して動作確認します。

% firebase serve --only functions:helloWorld,hosting
hosting: Local server: http://localhost:5000
functions: helloWorld: http://localhost:5001/nuxt-ssr-xxxxx/us-central1/helloWorld

この状態で以下の URL にアクセスして下さい。public に一致する静的アセットが存在していない場合は Firebase functions に rewrite される事を確認して下さい。

http://localhost:5000/
http://localhost:5000/404.html
http://localhost:5000/hoge

ホスティングの優先順位については次の URL で確認して下さいhttps://firebase.google.com/docs/hosting/url-redirects-rewrites#section-priorities

Nuxt.js を Express のミドルウェアとして動作させる

Nuxt.js アプリケーションをスクラッチから作ります。

cd ~/nuxt-ssr-firebase/functions
yarn add nuxt
mkdir -p nuxt-app/pages
touch nuxt-app/pages/index.vue nuxt.config.js

create nuxt.config.js

module.exports = { 
  srcDir: 'nuxt-app',
  buildDir: 'nuxt-dist',
  build: {
    publicPath: '/assets/',
  }
}

create nuxt-app/pages/index.vue

<template>
  <h1>Hello world!</h1>
</template>

この状態で yarn run nuxt を実行して動作確認をして下さい。

Express のミドルウェアとして動作させる

次に functions/nuxt-server.js を作成します。

const { Nuxt, Builder } = require('nuxt')
const app = require('express')()

const port = process.env.PORT || 3000

const config = require('./nuxt.config.js')
config.dev = process.env.NODE_ENV === 'development'
const nuxt = new Nuxt(config)

app.get('/api/ping', (req, res) => {
  res.json({ ping: 'pong' }); 
});

app.use(async (req, res) => {
  await nuxt.ready()
  nuxt.render(req, res)
})

// Build only in dev mode with hot-reloading
if (config.dev) {
  new Builder(nuxt).build()
    .then(listen)
    .catch((error) => {
      console.error(error)
      process.exit(1)
    })
}

function listen () {
  console.log('=== listen ===')
  // Listen the server
  app.listen(port, '0.0.0.0')
  console.log('Server listening on `localhost:' + port + '`.')
}

module.exports = app

NODE_ENV=development node nuxt-server.js を実行した場合は nuxt.config.js で指定した srcDir を Hot Reloading します。node nuxt-server.js を実行する場合は最初に yarn nuxt build を実行してから nuxt.config.js で指定した buildDir にファイルを生成して置く必要があります。

次に functions/index.js を編集します。

 const functions = require('firebase-functions');
+const nuxtServer = require('./nuxt-server');

+
+exports.nuxtServer = functions.https.onRequest(nuxtServer)

yarn nuxt build を実行した後で firebase serve --only functions:nuxtServer を実行して動作確認をします。

Nuxt.js を Firebase にデプロイする時の注意点

ところで以下のファイルは Firebase Hosting から配信すべきファイルですが、今のままだとFirebase Functions が動的に配信を行っています。

% tree nuxt-dist/dist/client/
nuxt-dist/dist/client/
├── 3ea0a886292a84605c64.js
├── 70744f471384cdfbb566.js
├── 7d3682722017aec6af25.js
├── LICENSES
└── afc01472d20742d8c1ac.js

なのでこちらのファイルは public/assets に移動します。以下のコマンドを実行してください。

rm -fr ../public/*
cp -R nuxt-dist/dist/client/ ../public/assets

次に firebase.json を以下の様に修正します。

     "rewrites": [
       {   
         "source": "**",
-        "function": "helloWorld"
+        "function": "nuxtServer"
       }   
     ]

この状態で firebase serve --only functions:nuxtServer,hosting を実行して動作確認をして下さい。ちなみに public/assets のファイルを Hosting できている場合は console に以下の様なメッセージが表示されます。

127.0.0.1 - - [25/Dec/2018:02:50:39 +0000] "GET /assets/3ea0a886292a84605c64.js HTTP/1.1" 200 143472 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36"

もし functions が動的に返している場合は以下の様に表示されます。

[hosting] Rewriting /assets/3ea0a886292a84605c64.js to local function nuxtServer
info: User function triggered, starting execution
info: Execution took 17 ms, user function completed successfully
127.0.0.1 - - [25/Dec/2018:02:51:24 +0000] "GET /assets/3ea0a886292a84605c64.js HTTP/1.1" 200 - "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36"

こちらのコマンドは functions/package.json にまとめて記述した方が良いでしょう。

-    "logs": "firebase functions:log"
+    "logs": "firebase functions:log",
+    "prebuild": "rm -fr ../public/*",
+    "build": "nuxt build",
+    "postbuild": "cp -R nuxt-dist/dist/client/ ../public/assets"

Windows User の方などは cpx, rimraf などを適宜活用頂ければと思います。

最後に firebase deploy --only functions:nuxtServer,hosting を実行してデプロイが出来ていることを確認して下さい。

まとめ

というわけで上記の構成でプロジェクトを作成した場合は以下の様に作業する事ができます。

  • 開発時: NODE_ENV=development node nuxt-server.js
  • デプロイ前の動作確認: firebase serve --only functions:nuxtServer,hosting
  • デプロイ: firebase deploy --only functions:nuxtServer,hosting

実際に業務で開発を行う場合はデプロイ先を staging, production と分けた方が良いと思いますし、Firebase と Nuxt.js のディレクトリ構成などはまだ検討の余地があると思いますが、その辺りについては後世のエンジニアがきっと素晴らしい記事を書いてくれると思います。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした