【Gatsbyで作るSPA】 Client-only Routes を設定する

##[Gatsby](https://www.gatsbyjs.org)でSPAをつくろうとすると
Gatsby.jsは「爆速サイト生成」として有名な静的サイトジェネレータで、ブログなどの作成管理などで注目されています。
また、その性能からSPAの作成にも有効だと書かれている情報サイト多くあります。
しかし、実際にSPAを作ろうとすると、SPA特有の設定などが必要になります。日本語での制作例や設定例などはまだ少なく、まとめサイトのようなものも見つけにくい状況です。
ここでは、実際に作ってみて分かった、最低限必要な設定などをまとめました。

##試行環境
| ソフトウェア | バージョン |
|:-:|:-:|
| MasOS   | 10.10.5 |
| Node.js | 10.16.1 |
| React   | 16.12.0 |
| Gatsby  | 2.19.7  |

##準備
まずはプロジェクトを作成します。

```
$ npx gatsby new spa_study
$ cd spa_study
$ npm start
```

localサーバーが起動するので、ブラウザから`localhost:8000`にアクセスして開発中の画面を確認します。

##ルーティング用のページを作る
`src/pages`に新しいページ`users.js`を作成します。
この`users.js`に、ユーザー情報をURLに応じて表示するアプリを作っていきます。

`index.js`の任意の場所に`users.js`へのリンクを追加します

```react:index.js
    <Link to="/users/">Go to user list</Link>
```

##@reach-router
`gatsby`には`@reach-router`がすでに組み込まれています。ルーティング関連はこのライブラリを使用していきます。
`user.js`を以下のようにします。

```react:users.js
import React from "react"
import { Router, Link } from '@reach/router'

import Layout from "../components/layout"
import SEO from "../components/seo"

const UsersPage = () => (
  <Layout>
    <SEO title="Users" />
    <Router basepath="users">
      <UserList path="/" />
      <User path=":userId" />
    </Router>
  </Layout>
)

export default UsersPage

const UserList = () => (
  <div>
    <h2>ユーザ一覧</h2>
    <li><Link to="hoge">Hoge</Link></li>
    <li><Link to="hage">Hage</Link></li>
  </div>
)

const User = props => (
  <div>
    <h2>User:{props.userId}</h2>
    <Link to="../">Back</Link>
  </div>
)
```

`<Link />`コンポーネントを`gatsby`から呼び出すか`@reach-router`から呼び出すかで挙動がかわるので注意が必要です。
さて、ユーザ一覧のページと各ユーザへのリンクができましたが、この状態でリンク先へいくと、以下のようなエラーが出ます。

```javascript
  170 | if (document.title) {
  171 |   pageName = document.title
  172 | }
> 173 | const pageHeadings = document
  174 |   .getElementById(`gatsby-focus-wrapper`)
  175 |   .getElementsByTagName(`h1`)
  176 | if (pageHeadings && pageHeadings.length) {
```

`gatsby`では、pagesフォルダに作られたファイルを元に静的ファイルが作成され、サイトを構成します。そのため、存在しないファイルにアクセスしようとするとエラーが出ます。
通常は404エラーとなりますが、`<Router />`がクライアントサイドで振り分けているためにjavascriptエラーとなるようです。

##gatsby-node.js
上記のjavascriptエラーを回避するために、`gatsby`の設定を変更する必要があります。
`gatsby-node.js`で使われる`onCreatePage`でpagesにはないページを処理します。

```javascript:gatsby-node.js
exports.onCreatePage = async ({ page, actions }) => {
	const { createPage } = actions
	if (page.path.match(/^\/users/)) {
		page.matchPath = "/users/*"
		createPage(page)
	}
}
```

以上を書き加えたのち、localサーバーを再起動すると、ユーザーページへのアクセスが可能になります。
コード中に二回出てくる`/users/`が、クライアントサイドで生成されるURLの基点となります。
この設定はプラグインの`gatsby-plugin-create-client-paths`を使うことでより簡単になります。

##Serverのルーティング設定
SPAでルーティングを制御する場合、URLで指定された場所にはファイルが存在していません。
お気に入りや、手打ちなどでダイレクトにアクセスしようとすると404エラーに遭遇します。
それに対応するためには、サーバー側で、すべてのアクセスを`index.html`などに集約する必要があります。

しかし、`gatsby`で作ったSPAは、`create-react-app`などで作成した従来のReactSPAと異なり、静的ページが複数生産されています。
そのため、すべてのアクセスをトップページへ誘導する方法ではルーティングの不具合を解消できない場合があります。
今回作ったアプリの場合、`<Router />`が記述されているページが`users.js`なので、`/users/`以下のアクセスを`/users/`へ誘導する必要があります。
ここでは、apacheの設定を紹介します。

```apache:.htaccess
  RewriteEngine on
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteRule ^users/. users/index.html [L]
```

###3/26追記
個別のディレクトリにルールを適用しなくても、クライアントサイドオンリーを表示することができました。
.htaccessに以下の設定を追記することで表示されました。

```apache:.htaccess
  ErrorDocument 404 /404.html
```

``gatsby``でつくられる404.htmlに一旦転送されたあと、ウェブアプリ内のルーティング機能でレンダリングされました。

##開発環境と本番環境でURLの階層が異なる場合
開発用のlocalサーバと本番環境ではURLの構成が異なる場合があります。

- 開発：//myserver.com/index.html
- 本番：//rentalserver.com/myapp/index.html

この場合、ルートからの階層が異なるため、`@reach-router`でのルーティング条件が適合しなくなります。
その場合は、`basepath`の引数を`props`から取ることで回避できます。

```react:users.js
const UsersPage = (props) => (
  <Layout>
    <SEO title="Users" />
      <Router basepath={props.uri}>
      <Home path="/" />
      <User path=":userId" />
    </Router>
  </Layout>
)

```

##まとめ
`gatsby`でのSPAルーティングは同梱されている`@reach-router`を使用する。
動的ページへのアクセスには`gatsby-node.js`などでルーティングの設定が必要になる。
動的ページの元となっているページごとにサーバ側のルーティング処理を設定する必要がある。

`gatsby`でSPAを作成すると、pagesのフォルダ構成がサイトマップになるので、全体像を把握しやすく、機能をどんどん追加しても管理がしやすいのがメリットだと思います。
反面、URLを元にする動的ページが増えると、ルーティング処理の管理ができなくなっていきそうです。

爆速が魅力のgatsby SPAですが、`create-react-app`の方が設定項目も少なくて、SPAの入門にはよさそうです。

##参考
- [Client-only Routes & User Authentication](https://www.gatsbyjs.org/docs/client-only-routes-and-user-authentication/)
- [Reach Router](https://reach.tech/router)
- [gatsby-plugin-create-client-paths](https://www.gatsbyjs.org/packages/gatsby-plugin-create-client-paths/)