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などをあらかじめ生成しておくことが特徴です。
「静的サイトジェネレータ」の一般的な利点は以下のとおりです。
Gatsbyは、Reactベースの静的サイトジェネレータです。最新のフロントエンド技術を駆使し、高速に閲覧できるサイトを生成できることで有名です。
たとえば、Reactの公式サイトはGatsbyを使用したサイトですが、このサイトをDevToolsで観察しながら閲覧すると、ページをスクロールするのに応じてクリックしなくても通信が走ります。これは、Gatsbyのランタイムが、表示エリアにリンクがはいってきた時点でリンク先コンテンツをプリフェッチしメモリ中に読み込み、クリック時には瞬時に表示できるようにしているからです。このような高速化のための高度な工夫が各種行われています。
自分が当初Gatsbyについて理解できていなかったのは、Reactとの関係です。
静的サイトジェネレータというぐらいだから、GatsbyはReactコードをSSR(Server Side Rendering)のようにあらかじめレンダリングして、静的なHTMLを事前に生成するのかな、と思いました。だから、JSコードはビルド時のみに実行されて、閲覧時には実行されないのかな、と。
このように思った理由は、他の静的サイトジェネレータの動作からの類推で、たとえばRubyベースのJekyllなどは、ビルド時にテンプレートをRubyインタプリタで評価してHTMLを生成し、閲覧時には一切Rubyコードは実行されません(されたら静的にならない)。同じく、Gatsybyでも「JSコードはビルド時のみに実行されて、閲覧時には実行されない」のかなと。
しかし、調べると、そうではありませんでした。
GatsbyはSSR的な静的HTML生成に加えて、それと連動する通常のReactアプリも生成します。
Create React AppやNext.jsと同様に、Reactで開発するSPAの自由度を完全に具備するものです。
詳しく見てみましょう。
まず、Gatsbyの説明をする前に、Create React Appdを使ったときのReactアプリの生成と動作の様子を見てみます。下図のように、ビルド時にcreate-react-appコマンドを実行、bundle.jsを生成し、それを読み込むindex.htmlと合わせてデプロイします。Reactアプリは、ブラウザ中で初めて実行されます。
次に、Next.jsなどを用いたSSR(Server Side Rendering)を見てみましょう。
ブラウザが初期ページを読み込むタイミングで、サーバ側のNode.jsでReactアプリが実行され、初期HTMLを生成(SSR)します。ブラウザがそれを読み込んで初期表示し、引き続きReactアプリを実行し、仮想DOMの更新や、SPAとしての実行にうまいこと繋げてくれます。必要があればReduxのステートの転送なども行なわれます。
さて、Gatsbyです。Gatsbyでは、Reactアプリをビルド時に1回実行し、HTMLを生成しておきます。HTMLを生成する動作はSSRと同様なのですが、サーバ上ではなく、ビルドマシン上で実行することが異なります。このHTMLをJSと共にデプロイし、ブラウザはそれを初期ページとして読み込み、SSRと同様にReactアプリの実行が再度なされ、仮想DOM更新、SPAとしての実行、Reduxステートなどが引き継がれます。
そして、ブラウザ内でのReactアプリとしての実行は通常と同じで、API呼び出しを実行しても良いし、redux-sagaの実行など、任意の動作が可能です1。ただし、プリフェッチやコード分割にも関わるので、ルーティングはGatsbyの仕組みに従った方が良いでしょう2。Gatsbyでは、ルーティングは内部的にアクセシビリティ(a11y)向上のために@reach/routerが使用されています。
Material UIなどもプラグインを使用して使用可能です(コード例、参考になるページ)
上記まででも、サイト作成には十分に便利だと思いますが、Gatsbyのもう一つの大きな特徴は、ビルド時のさまざまな処理(データ取得と変換、使用)を「ビルド時GraphQL」で統一的に行えることです。
静的サイトジェネレータとしての典型的な処理は、Markdown形式のテキスト情報を、ファイルシステムから読み込んで、GraphQL経由で取得し、Reactコンポーネント内で表示することです。
しかし、Gatsbyではそれを上記のようにdata source, data transformerという枠組みで一般化することで、多様な処理を統一的にかつ簡潔に記述することができます。
ビルド時に形成されるGraphQL DBの内容は、ブラウザ内での実行時にはアクセスできません。これはビルド時だけのものです。
ちなみにたとえば、実行時にまったく別のGraphQLサーバにアクセスすることができます。
さてここで一つの疑問が浮かぶかもしれません。
ビルド時に形成されるGraphQL DBの内容は、閲覧時のブラウザ内のReactアプリからはアクセスできないとしたら、ビルド時に得られたGraphQLのクエリ結果の情報は、ブラウザ内のReactコンポーネントではどのように入手できるのでしょうか? それが取得できないかぎり、SPAとしてHTMLと同じ画面を再現することはできません。コードで確認したわけではないのですが、生成物を見るとGatsbyは以下のような処理をしているようです。
GraphQLをDBを作成するために、種々のdata transformer,data sourcesがプラグインとして利用可能です。
以下に、GraphQLを使ったGatsbyコードの例を示します。
例としては、gatsby-source-wordpressを用いてWordPressからAPIでビルド時に記事をとってくるところです。
取ってきた記事の内容を、以下のようにReactコンポーネントの内容に組み込みます。
上記の準備の上、プラグインの設定もした上で、以下のようにGatsbyプロジェクトをビルドできます。ビルド中にWordpress APIにアクセスしていることがわかります。
サイトの高速性や、CDNで大規模スケールさせることは、イントラ向けシステムやツール開発などでは必ずしも必要ではないかもしれません。しかし、それを除いたとしても、Gatsbyには以下の利点があります。
ちなみに、Gatsbyのような静的サイト生成を活用したWebシステムアーキテクチャを「JAMSatckアーキテクチャ」と呼ぶそうです。下図はhttps://jamstack.org/より引用。
以下がJAMStackべからず集です。
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アプリは、ブラウザ中で初めて実行されます。
一般的なSSR(Next.jsなど)の動作
次に、Next.jsなどを用いたSSR(Server Side Rendering)を見てみましょう。ブラウザが初期ページを読み込むタイミングで、サーバ側のNode.jsでReactアプリが実行され、初期HTMLを生成(SSR)します。ブラウザがそれを読み込んで初期表示し、引き続きReactアプリを実行し、仮想DOMの更新や、SPAとしての実行にうまいこと繋げてくれます。必要があればReduxのステートの転送なども行なわれます。
Gatsbyの動作
さて、Gatsbyです。Gatsbyでは、Reactアプリをビルド時に1回実行し、HTMLを生成しておきます。HTMLを生成する動作はSSRと同様なのですが、サーバ上ではなく、ビルドマシン上で実行することが異なります。このHTMLをJSと共にデプロイし、ブラウザはそれを初期ページとして読み込み、SSRと同様にReactアプリの実行が再度なされ、仮想DOM更新、SPAとしての実行、Reduxステートなどが引き継がれます。そして、ブラウザ内でのReactアプリとしての実行は通常と同じで、API呼び出しを実行しても良いし、redux-sagaの実行など、任意の動作が可能です1。ただし、プリフェッチやコード分割にも関わるので、ルーティングはGatsbyの仕組みに従った方が良いでしょう2。Gatsbyでは、ルーティングは内部的にアクセシビリティ(a11y)向上のために@reach/routerが使用されています。
Material UIなどもプラグインを使用して使用可能です(コード例、参考になるページ)
ビルド時GraphQL
上記まででも、サイト作成には十分に便利だと思いますが、Gatsbyのもう一つの大きな特徴は、ビルド時のさまざまな処理(データ取得と変換、使用)を「ビルド時GraphQL」で統一的に行えることです。静的サイトジェネレータとしての典型的な処理は、Markdown形式のテキスト情報を、ファイルシステムから読み込んで、GraphQL経由で取得し、Reactコンポーネント内で表示することです。
しかし、Gatsbyではそれを上記のようにdata source, data transformerという枠組みで一般化することで、多様な処理を統一的にかつ簡潔に記述することができます。
こんなこともできる
ビルド時に形成されるGraphQL DBの内容は、ブラウザ内での実行時にはアクセスできません。これはビルド時だけのものです。ちなみにたとえば、実行時にまったく別のGraphQLサーバにアクセスすることができます。
「ビルド時GraphQL」の結果をブラウザ内のReactコンポーネントにも渡す
さてここで一つの疑問が浮かぶかもしれません。ビルド時に形成されるGraphQL DBの内容は、閲覧時のブラウザ内のReactアプリからはアクセスできないとしたら、ビルド時に得られたGraphQLのクエリ結果の情報は、ブラウザ内のReactコンポーネントではどのように入手できるのでしょうか? それが取得できないかぎり、SPAとしてHTMLと同じ画面を再現することはできません。コードで確認したわけではないのですが、生成物を見るとGatsbyは以下のような処理をしているようです。
- ビルド時
- GraphQLクエリをビルド時実行
- クエリ結果を使ってReactアプリを静的HTMLにレンダリング
- このとき得られたGraphQLのクエリ結果はJSONで保存しておく。
- デプロイ時
- 上記で生成された静的HTMLをデプロイ
- 同時に、上で保存していたJSONも静的コンテンツとしてデプロイ
- ブラウザでのReactアプリ実行時
- 静的HTMLを初期表示
- 裏でReactアプリ実行、仮想DOMを再構築(SSRと同じ)
- 保存されたJSONを読み込み、同じ表示を再現する
Gatsby Plugins
GraphQLをDBを作成するために、種々のdata transformer,data sourcesがプラグインとして利用可能です。
コード例
以下に、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 } } } } } `
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/より引用。以下がJAMStackべからず集です。
- Wordpressを使わない
- ブラウジング時のSSRを使わない
- モノリシック
まとめ
- Gatsbyを「静的サイトジェネレータ」と呼んでしまうと、「動的サイト」は作れない、という印象をもってしまうかもしれないがそうではなく、React SPAとしてのすべての機能を発揮できる、CRAと同種の存在でもある3。
- CRAと同様に、babelやwebpackを呼び出す
- もちろん、Wordpress代替として爆速、CDNを駆使しスケールする、といった優れた性質をもっており、Gatsbyにとって「静的サイトをジェネレートすること」は主要な用途である。
- しかしながら、Gatsbyの有効性はそれに限られず、以下のような利点があり、着目したい。
- UI構築の一部をビルド時に移動
- ある種の「サーバレス」を推進
- GraphQLを駆使して、UIコードを、ビルド時の情報準備としても簡易化する
- UI構築の一部をビルド時に移動
コメント
コメントを投稿