Next.jsというReactアプリケーションをデフォルトでServerSideRenderingしてくれるライブラリがあります。
これをFirebaseHosting上にFirebaseFunctionsを使って構築することで、無料でサーバレスSPAを作る事ができます。
Next.jsをclone
Next.jsはサンプルがとても豊富です。
今回使うサンプルはwith-firebase-hostingです。
cloneしたらwith-firebase-hostingディレクトリをコピーして使いましょう。
Hello World
まずは依存ライブラリをインストールします。
npm i
そして
npm run next
でローカルサーバが立ち上がります。
デフォルトではlocalhost:3000ですね
これは単にnextjsを起動しているだけで、Firebaseは全く関係ありません。
デプロイ
事前にFirebaseでプロジェクトを作っておいてください。
プロジェクトを作ったら.firebasercの<project-name-here>をプロジェクト名に置き換えます
// .firebaserc { "projects": { "default": "<project-name-here>" } }
これでFirebaseの設定は完了です。
npm run deploy
これで設定したプロジェクトにデプロイされます。
(npm run serveは恐らく動かないと思います。)
デプロイが完了するとアップデート先のURLがログに出るのでアクセスしてみると、
先程ローカルで動かした内容が表示されてるかと思います。
もし404や500が出た場合はFirebase consoleのFunctionsでログを見ることができるので確認して見てください。
プロジェクトの構造
app
実際にコードを書いていくディレクトリです。
nextjsのルートディレクトリです。
普段使うnextjsと違うのはnext.config.jsファイルでコンパイルの保存先がfunctions/nextになっているところくらいでしょうか。
functions
デフォルトではこのディレクトリがFirebaseにデプロイされます。
appのコンパイル先でもあります。
public
これはFirebaseHostingのルートとなるディレクトリです。
placdholder.htmlというのが入ってますが、基本的に触ることはないです。
FirebaseHostingの設定でpublicは必須です。
存在していなければデプロイに失敗します。
Deployment Configuration | Firebase
firebase.json
Firebaseの細かい設定を書きます。
{ "hosting": { "public": "public", "rewrites": [ { "source": "**/**", "function": "next" } ] }, "functions": { "source": "functions" } }
デフォルトでは上のようになっています。
hostingの設定ですが、publicは必須でrewritewですべてのアクセスに対してFirebaseFunctionsのnext関数を起動するようになっています。
functionsの設定でFirebaseFunctionsのソースを指定しています。functions/index.jsの内容は
const functions = require('firebase-functions') const next = require('next') var dev = process.env.NODE_ENV !== 'production' var app = next({ dev, conf: { distDir: 'next' } }) var handle = app.getRequestHandler() exports.next = functions.https.onRequest((req, res) => { console.log('File: ' + req.originalUrl) // log the page.js file that is being requested return app.prepare().then(() => handle(req, res)) })
となっていて、exports.next
でnextをエクスポートしています。
writesの"function":"next"
はこのnextを呼んでいます。
面倒なところ
デフォルトではfunctionsがデプロイされますが、functionsに含まれるappのコンパイルの成果物distはpagesに紐づくファイルだけなので、
例えばapp/package.jsonのdependenciesはfunctions/package.jsonに書き足して上げないといけません。
画像を扱う場合はapp/staticというフォルダに入れて使うのがnextjsでは一般的なのですが、このstaticもコピーしなければなりません。
他にも、起動サーバーをカスタマイズする場合などもコピーして…
という感じになってしまいます。
appにまとめる
一応、今私はfunctionsの内容をappに移して運用しています。
そうなるとデプロイに不要なファイルが多く含まれるので、firebase.jsonのignoreで指定しています。
app/index.jsはfunctionsのindex.jsをそのまま使い、サーバーをカスタマイズする場合は
exports.start = () => { app.prepare().then(() => { createServer((req, res) => { ... }).listen(port, (err) => { if (err) throw err; console.log(`> Ready on http://localhost:${port}`); }); }); };
という感じにしました。
package.json
"scritps" : { "dev": "node -e \"require('./').start()\"", ... } "dependencies": { "firebase-admin": "^5.5.0", // 追加 "firebase-functions": "^0.7.3", // 追加 ... }
firebase.json
{ "hosting": { "public": "public", "rewrites": [ { "source": "**/**", "function": "next" } ], "ignore": [ "**/.*", "**/node_modules/**", "app/src", "app/flow-typed", "app/pages" ] }, "functions": { "source": "app" } }
package.json追記したりstaticコピーしてくるよりこっちのほうがいいなーと思いました。
よくわからない問題
firebase serveが上手く動かない
404だったり、変更が全然反映されなかったり…
謎です。
ローカルでは問題無く動くが、デプロイすると動かない
デプロイするとstyled-jsxとbabel-runtimeが無いというエラーが出ます。
ローカルではnode_modulesに入っているのですが…
追加でインストールすると動くようになりました。
まとめ
ハマリポイントありますが、無料でWebアプリ作れるのは良いですね〜
ちなみに、github pagesだった自分のポートフォリオをNext.js+FirebaseHostingに移行しました。
サーバレスなんで、アクセスの感覚が空くとやっぱり遅いけどインスタンスが起動しているときなら速度は問題無さそう。
まぁコンテンツ少ないんでね…
そのうちブログも移行します。