GatsbyでSPAサイトを作る

December 30, 2021

programming
article cover
Image by Sandro Katalina

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を使ってもらいたい!


Profile picture

元カメラマン。今はベルリンで働くWEBエンジニア。
スロバキア人の妻とドイツ猫二匹の多国籍家族。