Gatsby doesn’t generate SPA
GatsbyはReactをベースとしたSSG (Static Site Generator)で、非常に高速な静的サイトを生成してくれる。 が、あくまでページ毎に別々のHTMLが用意されるため、クライアントサイドでJavascriptによるルーティングがされているわけではない。静的サイトであってSPAではない。 そのせいかGatsbyはブログ等を作る際のツールのように思われているが、もちろん意図的にSPAを作ることができる。 そうなるとなぜそこまでしてGatsbyを使うのかと思う人もいるかもしれないが、個人的にはGatsbyは完全なReactの上位互換だと思っている。 GatsbyをSPAビルダーとして使ったとしても、Gatsbyにはまだ画像の最適化や様々なプラグインサポートといった協力なバックアップがあり、静的なサイトであれ、SPAであれ、Gatsbyを選ばない理由はない。
Stay on the root
デフォルトではGatsbyは/pagesフォルダー下に作ったファイル毎にURLを作ってくれる。そしてURLにアクセスした際に該当のHTMLを探して返却するという仕組みになっている。 このままではSPA内でルーティングするたびにGatsbyがHTMLを探しにいってしまい、404を返してしまうので、どのURLでもルートURLを参照するように設定する必要がある。 SPAはあくまで擬似的にルーティングしているだけで、実際にはルートから移動していないからだ。
gatsby-node.jsに次のように記述する。
// gatsby-node.js
exports.onCreatePage = ({ page, actions }) => {
const { createPage } = actions
if (page.path === `/`) {
page.matchPath = `/*`
createPage(page)
}
}
これでどのURLにアクセスしてもGatsbyはルートURLのHTMLしか返さない。
Use @reach/router
Gatsbyはもともと@reach/routerというルーティングライブラリーを内包しているので、これを使ってルーティングしていく。 使い方は以下の通りで、<Router>の中にルーティングしたいコンポーネント達を置いて、各コンポーネントが該当するURLをpath={”}プロパティで指定する。
import React from 'react'
import { Landing } from './landing'
import { UserPage } from './userPage'
import { PostList } from './postList'
export const App = () => {
return (
<Router>
<Landing path="/" />
<UserPage path="/user" />
<PostList path="/posts" />
</Router>
)
}
Only render necessary components
GatsbyでSPA作るだけなら、つまりクライアントサイドでルーティングするだけなら上記までのコードで達成できている。 でも必要なコンポーネントを必要な時にだけレンダリングすることで、パフォーマンスをあげることができる。
まずはReact.lazyを使うことで、動的にコンポーネントをインポートするよう設定する。
const UserPage = React.lazy(() => import('./userPage'))
const PostList = React.lazy(() => import('./postList'))
動的にインポートされているコンポーネントはReact.Suspenseで受け止める必要があるので、動的にインポートしたコンポーネントをレンダリングする専用のコンポーネントを作成する。
const LazyComponent = ({ component: Component, ...props }) => {
return (
<React.Suspense fallback={<Spinner />}>
<Component {...props} />
</React.Suspense>
)
}
あとはこのLazyComponentを使って、以下のように記述すれば完成。
import React from 'react'
import { Landing } from './landing'
const UserPage = React.lazy(() => import('./userPage'))
const PostList = React.lazy(() => import('./postList'))
export const App = () => {
return (
<Router>
<Landing path="/" />
<LazyComponent component={UserPage} path={'/user'} />
<LazyComponent component={PostList} path={'/posts'} />
</Router>
)
}
注意点
React.lazyでインポートするコンポーネントはdefault exportである必要があるので注意!
React.lazy(() => import('./test'))
// test.jsx
export const Test = () => <div>This won't work</div> // ダメな例
const Test = () => <div>This one works</div> // これなら動く
export default Test
gatsby-node.jsの記述を工夫すれば、静的サイトの中に部分的にSPAを構築することも可能なので、 より多くの人にGatsbyを使ってもらいたい!