Next.jsをCloud FunctionsにDeployする

Cloud Functionsは、イベントドリブンサーバーレスプラットフォームです。コードをuploadすると、HTTPリクエスト経由で呼び出すことができます。



Next.jsで作ったクライアントサイドのアプリをホスティングします。

これは、「[Cloud Functions を使用した動的コンテンツの配信とマイクロサービスのホスティング](https://firebase.google.com/docs/hosting/functions
)」というやり方らしいです。

# プロジェクトの作成

https://github.com/vercel/next.js/tree/canary/examples/with-firebase-hosting

を参考に作成します。

node -v: v15.9.0
だった。versionについては調べずにざっくり作業していきます。

```
npx create-next-app --example with-firebase-hosting with-firebase-hosting-app
```


`.firebaserc`にプロジェクトIDを書き込む。

```.firebaserc
{
  "projects": {
    "default": "hoge-XXXXXX"
  }
}
```

```
firebase use default
```

で、defaultプロジェクトを現在の「アクティブ プロジェクト」として使用します。

https://firebase.google.com/docs/cli?hl=ja

# Deploy

```
npm run deploy
```

```
> deploy
> firebase deploy --only functions,hosting


=== Deploying to 'hoge-XXXXXX'...

i  deploying functions, hosting
Running command: npm --prefix "$PROJECT_DIR" install

up to date, audited 1020 packages in 2s

72 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities
Running command: npm --prefix "$PROJECT_DIR" run build

> build
> next build src/

info  - Using webpack 5. Reason: Enabled by default https://nextjs.org/docs/messages/webpack5
info  - Checking validity of types  
warn  - No ESLint configuration detected. Run next lint to begin setup
info  - Creating an optimized production build  
info  - Compiled successfully
info  - Collecting page data  
info  - Generating static pages (4/4)
info  - Finalizing page optimization  

Page                             Size     First Load JS
┌ ○ /                            1.76 kB        68.7 kB
├ ○ /404                         194 B          67.1 kB
└ ○ /about                       1.76 kB        68.7 kB
+ First Load JS shared by all    66.9 kB
  ├ chunks/framework.65bc65.js   42 kB
  ├ chunks/main.870523.js        23.1 kB
  ├ chunks/pages/_app.9bccc5.js  977 B
  └ chunks/webpack.61f1b6.js     778 B

λ  (Server)  server-side renders at runtime (uses getInitialProps or getServerSideProps)
○  (Static)  automatically rendered as static HTML (uses no initial props)
●  (SSG)     automatically generated as static HTML + JSON (uses getStaticProps)
   (ISR)     incremental static regeneration (uses revalidate in getStaticProps)

✔  functions: Finished running predeploy script.
i  functions: ensuring required API cloudfunctions.googleapis.com is enabled...
i  functions: ensuring required API cloudbuild.googleapis.com is enabled...
✔  functions: required API cloudfunctions.googleapis.com is enabled
⚠  functions: missing required API cloudbuild.googleapis.com. Enabling now...

Error: Your project hoge-XXXXX must be on the Blaze (pay-as-you-go) plan to complete this command. Required API cloudbuild.googleapis.com can't be enabled until the upgrade is complete. To upgrade, visit the following URL:
```

>  Your project hoge-XXXXX must be on the Blaze (pay-as-you-go) plan to complete this command. Required API cloudbuild.googleapis.com can't be enabled until the upgrade is complete. To upgrade, visit the following URL:

「Blazeプランじゃないとコマンドは完了できないよ」という文言が丁寧に表示されます。従量課金プランじゃないとダメなようです。

https://qiita.com/azukiazusa/items/edd6ca9cba2d48c4c3e2

# Blazeプランに変更した

```
npm run deploy
```

を再度試みる。


```
> deploy
> firebase deploy --only functions,hosting


=== Deploying to 'hoge-XXXXXX'...

i  deploying functions, hosting
Running command: npm --prefix "$PROJECT_DIR" install

up to date, audited 1020 packages in 2s

72 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities
Running command: npm --prefix "$PROJECT_DIR" run build

> build
> next build src/

info  - Using webpack 5. Reason: Enabled by default https://nextjs.org/docs/messages/webpack5
info  - Checking validity of types  
warn  - No ESLint configuration detected. Run next lint to begin setup
info  - Creating an optimized production build  
info  - Compiled successfully
info  - Collecting page data  
info  - Generating static pages (4/4)
info  - Finalizing page optimization  

Page                             Size     First Load JS
┌ ○ /                            1.76 kB        68.7 kB
├ ○ /404                         194 B          67.1 kB
└ ○ /about                       1.76 kB        68.7 kB
+ First Load JS shared by all    66.9 kB
  ├ chunks/framework.65bc65.js   42 kB
  ├ chunks/main.870523.js        23.1 kB
  ├ chunks/pages/_app.9bccc5.js  977 B
  └ chunks/webpack.61f1b6.js     778 B

λ  (Server)  server-side renders at runtime (uses getInitialProps or getServerSideProps)
○  (Static)  automatically rendered as static HTML (uses no initial props)
●  (SSG)     automatically generated as static HTML + JSON (uses getStaticProps)
   (ISR)     incremental static regeneration (uses revalidate in getStaticProps)

✔  functions: Finished running predeploy script.
i  functions: ensuring required API cloudfunctions.googleapis.com is enabled...
i  functions: ensuring required API cloudbuild.googleapis.com is enabled...
⚠  functions: missing required API cloudbuild.googleapis.com. Enabling now...
✔  functions: required API cloudfunctions.googleapis.com is enabled
✔  functions: required API cloudbuild.googleapis.com is enabled
i  functions: preparing . directory for uploading...
i  functions: packaged . (9.91 MB) for uploading
✔  functions: . folder uploaded successfully
i  hosting[hoge-XXXXXX]: beginning deploy...
i  hosting[hoge-XXXXXX]: found 0 files in public
✔  hosting[hoge-XXXXXX]: file upload complete
i  functions: creating Node.js 14 function nextjsFunc(us-central1)...
✔  functions[nextjsFunc(us-central1)]: Successful create operation. 
i  functions: cleaning up build files...
Function URL (nextjsFunc(us-central1)): https://us-central1-hoge-XXXXXX.cloudfunctions.net/nextjsFunc
i  hosting[hoge-XXXXXX]: finalizing version...
✔  hosting[hoge-XXXXXX]: version finalized
i  hosting[hoge-XXXXXX]: releasing new version...
✔  hosting[hoge-XXXXXX]: release complete

✔  Deploy complete!

Project Console: https://console.firebase.google.com/project/hoge-XXXXXX/overview
Hosting URL: https://hoge-XXXXXX.web.app
```

Hosting URLにアクセスすると確かに表示されました。

![スクリーンショット 2021-08-17 11.46.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88357/74e65503-1dcb-ea03-f8b0-c5e8953f100a.png)

# SSGとSSR

Next.jsはSSRとSSGに対応しているのでその辺の挙動を確認してみます。

## SSGの確認

`src/index.jsx`を以下のように変更します。

```src/index.jsx
import App from "../components/App";

export default function Home(props) {
  return (
    <App>
      <div>
        <div>{props.isSSR ? <h2>SSR Working</h2> : <h2>SSR Not Works</h2>}</div>

        <div>
          {props.isStatic ? <h2>Static generated</h2> : <h2>is Not Static</h2>}
        </div>
      </div>
    </App>
  );
}

export async function getStaticProps() {
  return { props: { isStatic: true } };
}
```

ローカルで動かした場合

```
npm run dev
```

![スクリーンショット 2021-08-17 12.08.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88357/4bbaa1f2-8885-8085-d438-692260e6c320.png)

Firebaseにdeployしてみます。

```
npm run deploy
```

![スクリーンショット 2021-08-17 12.12.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88357/903a369b-054f-4de5-8002-2f69ead48868.png)

URLをうすしてないので無意味な画像ですが、確かに、Static Generatedと表示されています。

# SSRの確認

```src/index.jsx
import App from "../components/App";

export default function Home(props) {
  return (
    <App>
      <div>
        <div>{props.isSSR ? <h2>SSR Working</h2> : <h2>SSR Not Works</h2>}</div>

        <div>
          {props.isStatic ? <h2>Static generated</h2> : <h2>is Not Static</h2>}
        </div>
      </div>
    </App>
  );
}

export async function getServerSideProps() {
  return { props: { isSSR: true } };
}
```

ローカルで動かす

```
npm run dev
```

![スクリーンショット 2021-08-17 12.15.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88357/6efc2e84-8a5a-edd9-7c8d-47ca7febe7b8.png)

Deployしてみましょう。

![スクリーンショット 2021-08-17 12.20.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88357/9f6ec250-8384-6fca-3c71-bb24794f384d.png)

まあ、ローカルと一緒ですね。

`getServerSideProps`と`getStaticProps`は同時に使えません。ビルドエラーが出ます。

# 理解する。

ルートディレクトリに`firebaseFunctions.js`なるファイルがあります。

```firebaseFunctions.js
const { join } = require('path')
const { https } = require('firebase-functions')
const { default: next } = require('next')

const nextjsDistDir = join('src', require('./src/next.config.js').distDir)

const nextjsServer = next({
  dev: false,
  conf: {
    distDir: nextjsDistDir,
  },
})
const nextjsHandle = nextjsServer.getRequestHandler()

exports.nextjsFunc = https.onRequest((req, res) => {
  return nextjsServer.prepare().then(() => nextjsHandle(req, res))
})
```

`nextjsFunc`という関数が定義されています。これは、`firebase.json`で`hosting.rewrites`の中の`functions`で宣言されています。

```firebase.json
{
  "hosting": {
    "public": "public",
    "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
    "rewrites": [
      {
        "source": "**",
        "function": "nextjsFunc"
      }
    ]
  },
  "functions": {
    "source": ".",
    "predeploy": [
      "npm --prefix \"$PROJECT_DIR\" install",
      "npm --prefix \"$PROJECT_DIR\" run build"
    ],
    "runtime": "nodejs14"
  }
}
```

`predeploy`でinstallやbuildをしています。

Cloud Functionsでのリクエストの処理がどういう挙動になっているかというと、

- まずFirebase Hostingにリクエストが送られる
- リクエストの送り先が、`nexjsFunc`というCloud Functionsの関数へと書き換えられる
- `firebaseFunctions.js`の中の`nexjsFunc`関数がリクエストを処理し、Next.jsのサーバーとしてレンダリングし、レスポンスを返す

となっているようです。

`firebaseFunctions.js`の正体は、Cloud Function For Firebaseにデプロイするjsファイルでした。`package.json`のなかで`main`keyのところで設定されています。

```package.json
{
  "private": true,
  "main": "firebaseFunctions.js",
}
```

他にもいろいろ、Firebaseの設定ファイルがあります。Next.jsのレポジトリにドキュメントがあるので見てみましょう。

https://github.com/vercel/next.js/tree/canary/examples/with-firebase-hosting#good-to-know




この辺を参考にしました。

https://qiita.com/centerfield77/items/349ae9094872168ae86f#firebasefunctionsjs-%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B

# 参考


- https://qiita.com/1amageek/items/0d01dca2148df345224a
- https://qiita.com/takaken/items/8ed671486de804696079
- https://qiita.com/centerfield77/items/349ae9094872168ae86f
- https://dev.to/rowaxl/what-i-struggled-with-next-js-using-firebase-hosting-and-enable-ssr-4e67
- https://zenn.dev/ucwork/articles/67663455297629