Next.js 9.3新API getStaticProps と getStaticPaths と getServerSideProps の概要解説

# はじめに
この記事ではNext.jsにvesrion 9.3から導入された以下の3つのAPIについて解説します。

- `getStaticProps`
- `getStaticPaths`
- `getServerSideProps`

こちらのissueで議論されてきた機能です。
[Static Generation / SSG Improvements · Issue #9524 · zeit/next.js](https://github.com/zeit/next.js/issues/9524)
GitHub追っていると`GS(S)P methods`と略されているのを見ます。

Next.jsが未経験の方でもReactとSSR(Server Side Rendering)の基本的な知識があれば理解できると思います。
Next.jsを「SSRを簡単に実現できるフレームワーク」と捉えてる方は是非読んでみてください。

## ~~注意事項~~
~~現在の最新安定版v9.2.2では使用できません。v9.2.3-canary.16から使用できます。
[Release v9.2.3-canary.16 · zeit/next.js](https://github.com/zeit/next.js/releases/tag/v9.2.3-canary.16)
そのためvesrion 9.3.0のリリース時には仕様変更が伴う可能性があります。
とは言えレポジトリを追う限りここから大きく仕様が変更されることはないと思います。何か変更があれば追記する予定ですので心配な方はリリース後に読むことをオススメします。~~

（追記）
[Blog - Next.js 9.3 | Next.js](https://nextjs.org/blog/next-9-3)
リリースされました🎉🎉
この記事に大きな変更はありません。公式のリリースブログやドキュメントも充実しているので必ず目を通しましょう。

（さらに追記）
[Polyfill fetch by default by timneutkens · Pull Request #12353 · zeit/next.js](https://github.com/zeit/next.js/pull/12353)

> Previously users would need to import isomorphic-fetch or isomorphic-unfetch, this won't be needed once this has landed.

このPRで`isomorphic-unfetch`や`node-fetch`をインストールする必要がなくなります。
本文は特に変更する予定はありませんが適宜対応してください。

# tl;dr
- `getStaticProps`と`getStaticPaths`は**SSG用**のAPI
- `getServerSideProps`は**SSR用**のAPI
- ~~`getInitialProps`は非推奨になる~~

> No deprecations are introduced and getInitialProps will continue to function as it currently does. We do encourage adopting these new methods on new pages and projects.

(追記：リリースブログによると非推奨になるわけではないようです。新規に`getInitialProps`を使用するケースはあまりないと思います。)

# （前提知識） SSG と getInitialProps と next export
前提知識となるSSG、`getInitialProps`、`next export` についておさらいです。既にNext.jsに慣れている方は適宜読み飛ばしてください。また9.3のリリース以降にNext.jsを使い始めた方も真剣に読む必要はありません。

## SSG

Next.jsではpagesディレクトリ内のファイル名に基づいてルーティングされます。
[Pages - Documentation | Next.js](https://nextjs.org/docs/basic-features/pages)


```jsx:pages/welcome.js
function Welcome() {
  return <div>ようこそ Next.js へ！</div>
}

export default Welcome
```

上記のファイルを生成し`/welcome`にアクセスすると以下のように表示されます。

```html:
<div id="__next">
   <div>ようこそ Next.js へ！</div>
</div>
```
この時`pages/welcome.js`の出力は**常に同じ**です。`/welcome`にアクセスすると上記のHTMLが必ず表示されます。そのためアクセスの度にレンダリング（SSR）する必要がありません。

Next.jsはこのようなケースでは自動的に静的なファイルをビルド時に生成(SSG)します。
これは**Automatic Static Optimization**と呼ばれます。

[next.js/automatic-static-optimization.md at v9.2.2 · zeit/next.js](https://github.com/zeit/next.js/blob/v9.2.2/docs/advanced-features/automatic-static-optimization.md)

> Next.js automatically determines that a page is static (can be prerendered) if it has no blocking data requirements. This determination is made by the absence of getInitialProps in the page.

つまりpagesフォルダ内のファイルに`getInitialProps`の存在をチェックし、存在しなければ事前に生成が可能と判断します。この`getInitialProps`とはどんなメソッドなのでしょうか。

## getInitialProps

[next.js/data-fetching.md at v9.2.2 · zeit/next.js](https://github.com/zeit/next.js/blob/v9.2.2/docs/basic-features/data-fetching.md)

`getInitialProps`はページがレンダリングされる前に実行されるAPIです。該当のパスにリクエストがあると実行され、ページに必要なデータをpropsとして渡します。
（実際にはログを送信するなどのHTMLに影響しない副作用を行うこともある。）

今回紹介する3つのAPIと同様に**pagesフォルダ内のファイル**のみで使用できます。

`getInitialProps`がSSR専用のAPIというのは誤解です。
通常のアクセスの場合、`getInitialProps`がサーバー側で実行されます。
一方、`next/link`を使用してクライアントサイドルーティングした場合にはクライアント側で実行されます。
そのため`isomorphic-unfetch`といったクライアント側でもサーバー側でも動作するfetchライブラリが推奨されます。
 
```jsx:pages/stars.js
// クライアントサイドでもサーバーサイドでも使用するため
import fetch from 'isomorphic-unfetch'

// getInitialPropsで取得したスター数を受け取る
function Stars({ stars }) {
  return <div>Next stars: {stars}</div>
}

// 先に実行される
Stars.getInitialProps = async () => {
  const res = await fetch('https://api.github.com/repos/zeit/next.js')
  const json = await res.json()
  return { stars: json.stargazers_count }
}

export default Stars
```

ここではNext.jsのGitHubスター数を取得しています。returnされた値はpropsとして受け取り、divに反映させています。

生成されるHTMLは以下の通りです。

```html:
<div id="__next">
   <div>Next stars: 45601</div>
</div>
```
45601という値は執筆時点のものです。
リクエストの度にレンダリングが行われるため**アクセスするたびに変化する**可能性があります。

SSRは便利ですが欠点もあります。

- サーバーを管理する必要がある
- リクエストの度にHTMLを生成する
- 外部のAPIがダウンしているとgetInitialPropsが評価されないためHTMLが生成できない

こうした理由から事前に静的なファイルをビルドしておくことが求められてきました。

## next export 
外部に依存するデータをビルド時に取得しSSGするためには、これまで`next export`というコマンドが利用できました。`next build`と異なり`getInitialProps`が存在するページでもビルド時に評価をし、静的なファイルを生成することができます。
しかしこれには問題がありました。

`next link`を使用して`getInitialProps`を使用しているページにクライアントサイドでルーティングを行うと事前に`next export`で生成したファイルではなく再度クライアント側で`getInitialProps`が実行されてしまうのです。

これを解決するために新たなAPIが導入されました。


# getStaticProps
やっと本題です。
`getStaticProps`は`getInitialProps`が行っていた処理をビルド時に行い、静的なファイルを事前に生成するためのAPIです。`next export`と異なり`next link`でルーティングした場合でも静的なファイルを利用します。
このメソッドはクライアント側で実行されることはありません。**必ず事前にサーバーサイドで実行**されます。

```jsx:pages/buildTimeStars.js
// クライアントサイドでは実行されないため'isomorphic-unfetch'は必要ない
import fetch from 'node-fetch'

// getStaticPropsで取得したスター数とビルド時の時刻を受け取る
function BuildTimeStars({ stars, build_time }) {
  return <div>ビルド時（{build_time}）のNextのstar数は: {stars}</div>
}

// ビルド時に実行される
export async function getStaticProps() {
  const res = await fetch('https://api.github.com/repos/zeit/next.js')
  const json = await res.json()
  const stars = json.stargazers_count
  // ビルド時刻の取得
  const build_time = new Date().toString();

  return {
    props: {
      stars,
      build_time
    },
  }
}

export default BuildTimeStars
```

`/stars`と同様にNext.jsのGitHubスター数を表示するページです。ただし今回は`getStaticProps`を使用してビルド時の時刻もpropsとして渡しています。

このページは外部のAPIに表示結果が依存していますが**アクセスの度にSSRしません**。ビルド時にデータを取得し静的なファイルを事前に生成します。

表示されるHTMLは以下の通りです。

```html:
<div id="__next">
   <div>ビルド時（Sat Mar 07 2020 18:44:20 GMT+0000 (Coordinated Universal Time)）のNextのstar数は: 45601</div>
</div>
```
再ビルドしない限り**いつアクセスしても同じ結果**です。時刻やスター数はあくまでビルド時のものです。

---
`getStaticProps`の次は`getStaticPaths`ですがその前に Dynamic Routes についておさらいです。

# （前提知識） Dynamic Routes

[Dynamic Routes - Documentation | Next.js](https://nextjs.org/docs/routing/dynamic-routes)

pagesフォルダ内でファイル名にブラケット`[]`をつけるとダイナミックルーティングが有効になります。

```jsx:pages/posts/[pid].js
import { useRouter } from 'next/router'

const Post = () => {
  const router = useRouter()
  const { pid } = router.query

  return <p>Post: {pid}</p>
}

export default Post
```

```html:posts/abc
<div id="__next">
   <p>Post: abc</p>
</div>
```

```html:posts/hoge
<div id="__next">
   <p>Post: hoge</p>
</div>
```
上記のようにアクセスしたパスに応じて表示する内容が変更されます。

# getStaticPaths
`getStaticPaths`はDynamic Routes利用時にも静的なファイルを生成するためのAPIです。

下のファイルはこれまでと同様にGitHubスター数を表示するページです。
zeitが管理するレポジトリを30件取得しDynamic Routesを活用して静的なファイルを生成します。

```jsx:zeit/[name].js
import fetch from 'node-fetch'

function Zeit({ name, stars }) {
  return <div>{name} stars: {stars}</div>
}

// 最初に実行される。事前ビルドするパスを配列でreturnする。
export async function getStaticPaths() {
  // zeitが管理するレポジトリを(APIのデフォルトである)30件取得する
  const res = await fetch('https://api.github.com/orgs/zeit/repos')
  const repos = await res.json()
  // レポジトリの名前をパスとする
  const paths = repos.map(repo => `/zeit/${repo.name}`)
  // 事前ビルドしたいパスをpathsとして渡す fallbackについては後述
  return { paths, fallback: false }
}

// ルーティングの情報が入ったparamsを受け取る
export async function getStaticProps({ params }) {
  // ファイル名のzeit/[name].jsに対応
  const name = params.name
  const res = await fetch(`https://api.github.com/repos/zeit/${name}`)
  const json = await res.json()
  const stars = json.stargazers_count

  return { props: { name, stars } }
}

export default Zeit
```
これをビルドするとターミナルで実際にビルド対象になったパスが表示されます。

```bash
 ● /zeit/[name]                               
    ├ /zeit/ms
    ├ /zeit/micro
    ├ /zeit/test-listen
    └ [+27 more paths]
```
30のパスに対して事前に静的なファイルを生成したことが確認できます。実際にアクセスすると以下のように表示されます。

```html:/zeit/ms
<div id="__next">
   <div>ms stars: 2699</div>
</div>
```

```html:/zeit/micro
<div id="__next">
   <div>micro stars: 9128</div>
</div>
```

```html:/zeit/test-listen
<div id="__next">
   <div>test-listen stars: 115</div>
</div>
```
こちらも再ビルドしない限りいつアクセスしても結果は同じです。

## fallback とは

`getStaticPaths`は`paths`と`fallback`が必須パラメーターです。
`paths`は前述通り事前にビルドするパス対象を指定するためのもです。
`fallback`は事前ビルドしたパス以外にアクセスしたときの動作を決めるものです。

### false の場合
**404 page**となります。

### true の場合
先程の例ではGitHubのAPIを使って30のページを事前にビルドしました。しかしZEITが管理するレポジトリは30以上存在します。別のレポジトリの名前でアクセスした場合には事前にビルドされていなくてもページを正しく表示させたいです。

その場合以下のように`fallback`を設定します。

```jsx:zeit/[name].js
import fetch from 'node-fetch'
+ import { useRouter } from 'next/router'

function Zeit({ name, stars }) {
+  const router = useRouter()
+  if (router.isFallback) {
+   return <div>Loading...</div>
+  }

  return <div>{name} stars: {stars}</div>
}

export async function getStaticPaths() {
  // 省略
+   return { paths, fallback: true } // trueに
}

export async function getStaticProps({ params }) {
  // 省略
}

export default Zeit
```
`fallback`を`true`にすることにより事前にビルドしてないパスにアクセスされても404になりません。
以下がユーザーが事前にビルドしていないパスへアクセスしたときのフローです。

1. `useRouter`を使い一時的にpageのfallbackバージョンを表示させる
2. `getStaticProps`がサーバー側で実行され静的なファイルを生成する
3. 生成が完了したらfallbackバージョンから切り替える
4. 以後のリクエストに対しては事前にビルドしたページと同じように静的なファイルを返す

今回のケースでは取得した30件にhyper-siteが含まれていない場合には

```html:/zeit/hyper-site
<div id="__next">
   <div>Loading...</div>
</div>
```

上のように一時的にLoadingが表示されます。数秒後、サーバー側で`getStaticProps`の評価が正しく終了すると以下のように表示されます。

```html:/zeit/hyper-site
<div id="__next">
   <div>hyper-text stars: 243</div>
</div>
```

以後のアクセスではLoadingは表示されません。今回生成された静的なファイルを返します。

# getServerSideProps

最後は`getServerSideProps`です。とは言っても殆ど解説することがありません。
これまでの`getInitialProps`と似たSSRのためのAPIです。使い心地もあまり変わらないでしょう。
異なる点は**`getServerSideProps`は必ずサーバーサイドで実行**されます。

`getInitialProps`は`next link`でルーティングを行った場合にはクライアントサイドで実行されていました。
一方、`getServerSideProps`は`next link`でルーティングした場合にもサーバーサイドで実行するようにクライアントサイドから指示をし、実行結果が含まれるJSONファイルがサーバーから返却されます。このJSONファイルをつかってNext.jsがクライアントサイドでレンダリングを行います。

`getInitialProps`は状況によって実行環境が変化するためいくつかの弊害がありました。
~~そのため**`getInitialProps`は今後、非推奨になるようです。**~~
[next.js/getInitialProps.md at canary · zeit/next.js](https://github.com/zeit/next.js/blob/canary/docs/api-reference/data-fetching/getInitialProps.md)
> If you're using Next.js 9.3 or newer, you should use getStaticProps or getServerSideProps instead of getInitialProps.

(追記：冒頭でも記しましたが非推奨という表現は適切ではありませんでした。しかし、まずこのAPIを使うことを意識しましょう。)

# まとめ
Next.jsの3つの新APIについて解説しました。
`getInitialProps`を加えた4つのメソッドの実行環境とタイミングは以下の通りです。

||サーバーサイド|クライアントサイド|実行タイミング|
|---:|:---:|:--:|:--:|
|getStaticProps|◯|✗|ビルド時 (+ fallback=trueならリクエストに応じて)|
|getStaticPaths|◯|✗|ビルド時 (+ fallback=trueならリクエストに応じて)|
|getServerSideProps|◯|✗|リクエストに応じて|
|getInitialProps|◯|◯|リクエストに応じて|

Next.jsをあまり使ったことがない人はSSRの印象が強いようです。この記事からもわかる通りNext.jsは9.3以降SSGの機能が大幅に強化されます。
これから導入を考えている方はSSRが本当に必要かどうか判断しSSGで実現できないか考えてみると良いかもしれません。


## おまけ

### (導入予定)Incremental Static Generationについて
以下のRFCについてです。
[RFC: Incremental Static Generation · Discussion #11552 · zeit/next.js](https://github.com/zeit/next.js/discussions/11552)

現在はStatic Buildしたアプリケーションの一部に変更があった場合、アプリケーション全体を再ビルドする必要があります。
この新機能Incremental Static Generationは**ページ毎に再ビルド**を実現するものです。

「巨大なアプリケーションになる予定なので変更毎にビルドをするのが大変そう」と感じて導入を見送ろうとしている方は要チェックです。

### Next.jsでJamstack

「これJamstackでは？」と思った人も多いことでしょう。
Jamstackについて聞き馴染みの無い方は以下の記事がオススメです。
[JAMstackってなに？実践に学ぶ高速表示を実現するアーキテクチャの構成 - エンジニアHub｜若手Webエンジニアのキャリアを考える！](https://employment.en-japan.com/engineerhub/entry/2019/12/10/103000)

Next.jsを開発・運営するZEIT社のCEOであるGuillermo Rauch氏は去年のJSConf JPで 
[BUILDING AND DEPLOYING FOR THE MODERN WEB WITH JAMSTACK](https://jsconf.jp/2019/talk/guillermo-rauch) 
というタイトルで登壇されていました。
氏の個人ブログ[2019 in Review](https://rauchg.com/2020/2019-in-review)ではJamstackについてや**Static**がなぜ強力であるかについて記しています。

今後、Next.jsでJamstackなアプリケーションが増えていくのではないでしょうか。