blog.tawa.me

東京から福岡移住したWeb開発の人のブログ。適当に何でも置きます。

Serverless Next.jsをFirebaseに置いたときのメモ

Next.jsのv8からサーバーレス向けのビルドができるようになった。

Next.jsのビルド自体はインストラクション通りやればできるのだが、それをデプロイするときにちょっとハマったのでそれを簡単にメモしておく。

デプロイ先は使い慣れたFirebaseを使った。

自分のサイトもこの方法で運用している。

tawa.me

Next.jsでプロジェクトを作成

公式サイトを見ながらやればきっとできる。

nextjs.org

Firebase Functionsのディレクトリにビルド

Firebase Functionsのディレクトリ内にnextのビルドが向くように設定をする。

const withTypescript = require("@zeit/next-typescript");
module.exports = {
  ...withTypescript(),
  distDir: "functions/build",
  target: "serverless",
};

こうすることでFirebase Functionsからビルドしたjsが参照できるようになる。

Firebase Functionsで返す

ビルドしたJavaScriptとエンドポイントをマッピング

ビルドしたjsを使って実際Functionsからビルドしたjsが実行されるようにマッピングする。

const functions = require("Firebase-functions");
const indexPage = require("./build/serverless/pages/index");
// const messagePage = require("./build/serverless/pages/message");
const architecturePage = require("./build/serverless/pages/architecture");

exports.index = functions.https.onRequest((request, response) => {
  indexPage.render(request, response);
});

next自体はTypeScriptで書いているが、Firebase FunctionsはビルドされたnextのJavaScriptを見るだけなので、Firebase FunctionsはTypeScriptではなくJavaScriptでやるのがおすすめ。

ただrequireしてrenderするだけ。

Firebaseのルーティング設定

エンドポイントの設定

Firebaseはデフォルトでhostingにルーティングされるので、設定したFunctionsにredirectされるように記述する。

{
  "hosting": {
    "public": "public",
    "cleanUrls": true,
    "rewrites": [
      {
        "source": "/static/*",
        "destination": "/*"
      },
      {
        "source": "/",
        "function": "index"
      },
    ]
  },
  "functions": {
    "predeploy": ["yarn --prefix \"$RESOURCE_DIR\" build", "yarn export"],
    "source": "functions"
  }
}

画像などのアセットへのパス

画像はSSRでは含まれず、クライアント側から再度リクエストが飛んでくる。それを返すパスの設定もする必要がある。

実際のURLはstatic/assets/images/avatar.jpgという感じになる。つまり、next.jsでは画像など静的ファイルは/static/以下のファイルに入れる仕様なので、/static/*はhostingでいい感じに処理されるように/*にパスを書き換える。

Firebase hostingでは/public/が配信される設定なので、ビルド時に

cp -r static public/

するといい感じに配信される。

バンドルされたJSのパス

SSRなので処理レンダーはサーバー側でなされるが、それ以降はクライアント側でJSを読み込んで実行される。

そのJSをフェッチするリクエストは別途捌く必要がある。

URLを見てみると_next/static/hogehoge/pages/index.jsのようになっているので、public/_next/static/以下にビルドされたファイルが入っていれば配信されるはず。

なので、

cp -r functions/build/static/* public/_next/static

もビルド時にする必要がある。

つまりどうするのか

ただビルドしたやつをFunctionsにマッピングするだけではなく、静的ファイルの配信もビルドしたものをいい感じに配置していかなければ動かないので意外とめんどくさい。

他にも賢い方法があるかもしれないが、とりあえず以下を実行すればすべてビルドしてデプロイまでされるようになっている。

yarn deploy

package.jsonはこんな感じになっている。

{
  "scripts": {
    "dev": "next",
    "build": "next build",
    "export": "rm -rf public && mkdir public public/_next public/_next/static && cp -r static public/ && cp -r functions/build/static/* public/_next/static",
    "deploy": "yarn build && Firebase deploy"
  },
}

yarn exportに前述したファイルのcp処理を書いている。ちょっとディレクトリ消したり作ったり移動したりしていてあまりきれいじゃないのでどうにかしたいとは思っている。

そして、Firebaseのデプロイにフックして、事前にFunctionsのビルドとファイル移動が走るようになっている。

{
  "functions": {
    "predeploy": ["yarn --prefix \"$RESOURCE_DIR\" build", "yarn export"],
    "source": "functions"
  }
}

こうすることで汚いけれど、いい感じにFunctionsで捌きつつ静的ファイル配信ができるようになっているはず。