Reactベース静的サイトジェネレータGatsbyの真の力をお見せします

Reactベース静的サイトジェネレータGatsbyの真の力をお見せします:

NTTテクノクロスの上原です。

業務では、社内情報のReact製自前キュレーションサイトの構築を担当しています。過去に社外ブログにReactVRの記事を途中まで書いたりしましたが、ReactVRが思ったより流行らなくて放置してしまい、誠に申し訳ないです。

この記事はNTTテクノクロスAdvent Calendar24日目の記事であり、社内の勉強会で発表した内容をQiita記事として書きなおしたものです。タイトルは釣りです。


記事を書いた理由

Gatsby.js(以降、Gatsbyと表記)はさまざまな高速化テクニックを用いた「爆速サイト生成」でたいへん有名なツールですが、そのリッチな機能性は、たとえばイントラ内サイト、業務システム開発、ツール開発などでも十分に活用できるものだと思い、その可能性を紹介するために書きました。


「静的サイトジェネレータ」って何?

いわゆる「静的サイトジェネレータ(Static Site Generator, SSG)」は、CMS(コンテンツ管理システム)の一種です。代表的なものには以下があります。

他にも多くが実装・公開されています。

Webサイトを作成・構築に良く使われる、WordPressなどのCMSでは、記事の「閲覧時」に動的にサイト内容を生成しますが、静的サイトジェネレータは、閲覧時ではなく、「ビルド時」にHTMLやCSSなどをあらかじめ生成しておくことが特徴です。


一般的な利点

「静的サイトジェネレータ」の一般的な利点は以下のとおりです。

  • Webサイトのコンテンツを、サーバの設定や実行なしでAWS S3やGitHub pagesに置ける。これにより

    • アプリサーバやDBが落ちるといった事象によってサイト公開が停止することがない。
    • 処理負荷に強い
    • 動的CMS(Wordpress等)やアプリサーバ処理、DB処理に起因する脆弱性は回避できる
  • CDNと相性が良くスケールしやすい。
  • Gitでコンテンツ管理ができる


Gatsbyとは

Gatsbyは、Reactベースの静的サイトジェネレータです。最新のフロントエンド技術を駆使し、高速に閲覧できるサイトを生成できることで有名です。

たとえば、Reactの公式サイトはGatsbyを使用したサイトですが、このサイトをDevToolsで観察しながら閲覧すると、ページをスクロールするのに応じてクリックしなくても通信が走ります。これは、Gatsbyのランタイムが、表示エリアにリンクがはいってきた時点でリンク先コンテンツをプリフェッチしメモリ中に読み込み、クリック時には瞬時に表示できるようにしているからです。このような高速化のための高度な工夫が各種行われています。


わたしの疑問

自分が当初Gatsbyについて理解できていなかったのは、Reactとの関係です。

静的サイトジェネレータというぐらいだから、GatsbyはReactコードをSSR(Server Side Rendering)のようにあらかじめレンダリングして、静的なHTMLを事前に生成するのかな、と思いました。だから、JSコードはビルド時のみに実行されて、閲覧時には実行されないのかな、と。

このように思った理由は、他の静的サイトジェネレータの動作からの類推で、たとえばRubyベースのJekyllなどは、ビルド時にテンプレートをRubyインタプリタで評価してHTMLを生成し、閲覧時には一切Rubyコードは実行されません(されたら静的にならない)。同じく、Gatsybyでも「JSコードはビルド時のみに実行されて、閲覧時には実行されない」のかなと。

しかし、調べると、そうではありませんでした。


Gatsbyの特徴

GatsbyはSSR的な静的HTML生成に加えて、それと連動する通常のReactアプリも生成します。

Create React AppやNext.jsと同様に、Reactで開発するSPAの自由度を完全に具備するものです。

詳しく見てみましょう。


Create React App(CRA)の動作

まず、Gatsbyの説明をする前に、Create React Appdを使ったときのReactアプリの生成と動作の様子を見てみます。下図のように、ビルド時にcreate-react-appコマンドを実行、bundle.jsを生成し、それを読み込むindex.htmlと合わせてデプロイします。Reactアプリは、ブラウザ中で初めて実行されます。



output_751673bcbfe8ef280417264a4042c06d-0.png



一般的なSSR(Next.jsなど)の動作

次に、Next.jsなどを用いたSSR(Server Side Rendering)を見てみましょう。

ブラウザが初期ページを読み込むタイミングで、サーバ側のNode.jsでReactアプリが実行され、初期HTMLを生成(SSR)します。ブラウザがそれを読み込んで初期表示し、引き続きReactアプリを実行し、仮想DOMの更新や、SPAとしての実行にうまいこと繋げてくれます。必要があればReduxのステートの転送なども行なわれます。



output_751673bcbfe8ef280417264a4042c06d-1.png



Gatsbyの動作

さて、Gatsbyです。Gatsbyでは、Reactアプリをビルド時に1回実行し、HTMLを生成しておきます。HTMLを生成する動作はSSRと同様なのですが、サーバ上ではなく、ビルドマシン上で実行することが異なります。このHTMLをJSと共にデプロイし、ブラウザはそれを初期ページとして読み込み、SSRと同様にReactアプリの実行が再度なされ、仮想DOM更新、SPAとしての実行、Reduxステートなどが引き継がれます。



output_751673bcbfe8ef280417264a4042c06d-2.png


そして、ブラウザ内でのReactアプリとしての実行は通常と同じで、API呼び出しを実行しても良いし、redux-sagaの実行など、任意の動作が可能です1。ただし、プリフェッチやコード分割にも関わるので、ルーティングはGatsbyの仕組みに従った方が良いでしょう2。Gatsbyでは、ルーティングは内部的にアクセシビリティ(a11y)向上のために@reach/routerが使用されています。

Material UIなどもプラグインを使用して使用可能です(コード例参考になるページ)


ビルド時GraphQL

上記まででも、サイト作成には十分に便利だと思いますが、Gatsbyのもう一つの大きな特徴は、ビルド時のさまざまな処理(データ取得と変換、使用)を「ビルド時GraphQL」で統一的に行えることです。



output_751673bcbfe8ef280417264a4042c06d-3.png


静的サイトジェネレータとしての典型的な処理は、Markdown形式のテキスト情報を、ファイルシステムから読み込んで、GraphQL経由で取得し、Reactコンポーネント内で表示することです。

しかし、Gatsbyではそれを上記のようにdata source, data transformerという枠組みで一般化することで、多様な処理を統一的にかつ簡潔に記述することができます。


こんなこともできる

ビルド時に形成されるGraphQL DBの内容は、ブラウザ内での実行時にはアクセスできません。これはビルド時だけのものです。

ちなみにたとえば、実行時にまったく別のGraphQLサーバにアクセスすることができます。



output_751673bcbfe8ef280417264a4042c06d-4.png



「ビルド時GraphQL」の結果をブラウザ内のReactコンポーネントにも渡す

さてここで一つの疑問が浮かぶかもしれません。

ビルド時に形成されるGraphQL DBの内容は、閲覧時のブラウザ内のReactアプリからはアクセスできないとしたら、ビルド時に得られたGraphQLのクエリ結果の情報は、ブラウザ内のReactコンポーネントではどのように入手できるのでしょうか? それが取得できないかぎり、SPAとしてHTMLと同じ画面を再現することはできません。コードで確認したわけではないのですが、生成物を見るとGatsbyは以下のような処理をしているようです。

  1. ビルド時

    • GraphQLクエリをビルド時実行
    • クエリ結果を使ってReactアプリを静的HTMLにレンダリング
    • このとき得られたGraphQLのクエリ結果はJSONで保存しておく。
  2. デプロイ時

    • 上記で生成された静的HTMLをデプロイ
    • 同時に、上で保存していたJSONも静的コンテンツとしてデプロイ
  3. ブラウザでのReactアプリ実行時

    • 静的HTMLを初期表示
    • 裏でReactアプリ実行、仮想DOMを再構築(SSRと同じ)
    • 保存されたJSONを読み込み、同じ表示を再現する
まとめると、ビルド時に形成されたGarphQL DB全体は必要はないので、「クエリの結果」のみを合わせてデプロイし、ブラウザ内でもGraphQL DBクエリ結果取得とすり替えて使うことで、同じ動作を再現するというわけです。


Gatsby Plugins

GraphQLをDBを作成するために、種々のdata transformer,data sourcesがプラグインとして利用可能です。



output_751673bcbfe8ef280417264a4042c06d-5.png



コード例

以下に、GraphQLを使ったGatsbyコードの例を示します。

例としては、gatsby-source-wordpressを用いてWordPressからAPIでビルド時に記事をとってくるところです。

export default withRoot(withStyles(styles)(Top)) 
 
export const pageQuery = graphql` 
  query { 
    allWordpressPost { 
      edges { 
        node { 
          id 
          title 
          link 
          content 
          featured_media { 
            source_url 
          } 
        } 
      } 
    } 
  } 
` 
取ってきた記事の内容を、以下のようにReactコンポーネントの内容に組み込みます。

class BlogPosts extends React.Component<IProps> { 
  public render() { 
    const { classes, allWordpressPost } = this.props 
 
    return ( 
      <div 
        className={classNames(classes.layout)} 
        style={{ marginTop: '1rem', marginBottom: '1rem' }} 
      > 
        <Grid container={true} spacing={40}> 
          {allWordpressPost.edges.map(edge => { 
            const content = edge.node.content ? edge.node.content : '' 
            const strippedContent = content.replace(/<(?:.|\n)*?>/gm, '') 
 
            return ( 
              <Grid key={edge.node.id} item={true} xs={12} sm={6} md={4} lg={3}> 
                <ContentCard 
                  imageUrl={edge.node.featured_media.source_url} 
                  heading={edge.node.title} 
                  targetUrl={edge.node.link} 
                > 
                  <Typography component="p">{strippedContent}</Typography> 
                </ContentCard> 
              </Grid> 
            ) 
          })} 
        </Grid> 
      </div> 
    ) 
  } 
} 


Gatsbyビルドの実行

上記の準備の上、プラグインの設定もした上で、以下のようにGatsbyプロジェクトをビルドできます。ビルド中にWordpress APIにアクセスしていることがわかります。

% npm run build 
> gatsby-starter-default@1.0.0 build /Users/uehaj/work/201812/techhub-gatsby 
> gatsby build 
 
success open and validate gatsby-configs — 0.013 s 
success load plugins — 0.270 s 
success onPreInit — 4.173 s 
success delete html and css files from previous builds — 0.063 s 
success initialize cache — 0.006 s 
success copy gatsby files — 0.710 s 
success onPreBootstrap — 0.007 s 
⠂ source and transform nodes -> wordpress__POST fetched : 12 
⢀ source and transform nodes -> wordpress__PAGE fetched : 5 
⠐ source and transform nodes -> wordpress__wp_media fetched : 38 
⠁ source and transform nodes -> wordpress__wp_taxonomies fetched : 1 
⠄ source and transform nodes -> wordpress__CATEGORY fetched : 4 
⢀ source and transform nodes -> wordpress__TAG fetched : 13 
⠈ source and transform nodes -> wordpress__wp_users fetched : 4 
success source and transform nodes — 2.745 s 
success building schema — 0.798 
 
  : 


「爆速サイト」が必要ない場合でも得られるGatsbyの利点

サイトの高速性や、CDNで大規模スケールさせることは、イントラ向けシステムやツール開発などでは必ずしも必要ではないかもしれません。しかし、それを除いたとしても、Gatsbyには以下の利点があります。

  • 通常ならDBで保持する/手書き修正のところ、ビルド時にUIに組込める。たとえば、

    • 入力フォームの「組織一覧」の選択肢を、ビルド時に他のWebサイトやAPI、CSVなどから取得し・最新化する
    • インクリメンタルサーチの選択肢
    • 「運営からのお知らせ」情報
    • なんらかの巡回収集
  • データの入力やオーサリングをWordPressなどCMSにまかせ、表示をカスタム化することでシステム開発を単純化できる

    • しかもPHPを書かずに !!
    • この用途に特化したHeadless CMSというジャンルのプロダクトも出ている
  • CD(Continuous Delivery)と組合せると、有用性はさらにUP!

    • 「記事をWordPressで公開したタイミングでwebhookを叩いてビルド、デプロイ」など


JAMSatckアーキテクチャ1実装としてのGatsby

ちなみに、Gatsbyのような静的サイト生成を活用したWebシステムアーキテクチャを「JAMSatckアーキテクチャ」と呼ぶそうです。下図はhttps://jamstack.org/より引用。



スクリーンショット 2018-12-21 18.46.57.png


以下がJAMStackべからず集です。

  • Wordpressを使わない
  • ブラウジング時のSSRを使わない
  • モノリシック
クラウド、サーバーレス時代のアーキテクチャと言えるでしょう。


まとめ

  • Gatsbyを「静的サイトジェネレータ」と呼んでしまうと、「動的サイト」は作れない、という印象をもってしまうかもしれないがそうではなく、React SPAとしてのすべての機能を発揮できる、CRAと同種の存在でもある3

    • CRAと同様に、babelやwebpackを呼び出す
  • もちろん、Wordpress代替として爆速、CDNを駆使しスケールする、といった優れた性質をもっており、Gatsbyにとって「静的サイトをジェネレートすること」は主要な用途である。
  • しかしながら、Gatsbyの有効性はそれに限られず、以下のような利点があり、着目したい。

    • UI構築の一部をビルド時に移動

      • ある種の「サーバレス」を推進
    • GraphQLを駆使して、UIコードを、ビルド時の情報準備としても簡易化する
では、みなさん良いお年を‼️



  1. とはいえ、今ビルド中なのか、ブラウザ中なのか、や、ライフサイクルフックを通じて緻密な分岐処理が必要になることもある。 



  2. Next.jsでもルーティングはNext.jsのルータにまかせるのと同様に。 



  3. SPAのサイトを「静的サイト」と呼べるならそうだが、あまり聞かない。 


コメント

このブログの人気の投稿

投稿時間:2021-06-17 05:05:34 RSSフィード2021-06-17 05:00 分まとめ(1274件)

投稿時間:2021-06-20 02:06:12 RSSフィード2021-06-20 02:00 分まとめ(3871件)

投稿時間:2020-12-01 09:41:49 RSSフィード2020-12-01 09:00 分まとめ(69件)