Gatsbyチュートリアル
静的サイトジェネレータとして有名なGatsby。
触ったことが無かったが、ちょうど使用する機会があったため、公式チュートリアルを一通りやってみる。
その雑多なまとめ。
今回触れていること
公式チュートリアルはこちら www.gatsbyjs.com
とても親切に出来ていて助かる。
Gatsbyとは
この記事が分かりやすい。 qiita.com
ここも見とくと具体的に何が利点なのか分かる。 qiita.com
特徴としては以下が挙げられる。
- Reactで作られた静的サイトジェネレーター
- 静的サイトジェネレーターだが、axiosなどを使った、非同期のdata fetchも可能。
- GraphQLと相性良い
- Gatsbyプラグインが多数あり便利(TypeScript化,PWA対応...)
- 爆速
- サーバーを気にしないで済む
Reactを触ったことがある人ならすぐ使える。
学習コストがあまり高くないのは利点。
GraphQLは完全に把握するのは少し難易度高そうな感じ。
仕組みを大まかに把握して所で実際に触ってみる。
0. Set Up Your Development Environment
環境構築はチュートリアルに沿っていけば問題ない。
# gatsbyサイト新規作成 gatsby new [SITE_DIRECTORY_NAME] [URL_OF_STARTER_GITHUB_REPO] # 開発サーバー起動(http://localhost:8000/ で確認) gatsby develop
1. Get to Know Gatsby Building Blocks(Surgeを使用したデプロイ)
JSXの説明、Reactの説明がされているがこの部分はさっと読み流し。
「src/pages/」に設置するとページとして認識される。
試しに「src/pages/about.js」を作成し、「http://localhost:8000/about」で確認してみるとページが確認できる。
再利用できるコンポーネントはサブコンポーネントとして「src/components」に作る。
「src/components/header.js」を作成し、about.jsで読み込んでみるとheader.jsが/aboutに表示されていることが確認できる。
Gatsbyコンポーネント(Link)の使用は以下の様に行う。
import React from "react" import { Link } from "gatsby" import Header from "../components/header" export default function Home() { return ( <div style={{ color: `purple` }}> <Link to="/contact/">Contact</Link> <Header headerText="Hello Gatsby!" /> <p>What a world.</p> <img src="https://source.unsplash.com/random/400x200" alt="" /> </div> ) }
Gatsbyサイトで処理されないページへの外部リンクについては、通常のaタグで行うとのこと。
Surgeを使用したデプロイが紹介されていたので試す。
npm install --global surge # surgeアカウントがない場合は画面に従って作成 surge login # ビルドするとpublicフォルダに生成される gatsby build # surgeデプロイ surge public/ # domainに記載されているURLを開くとページが確認できる
surgeについてはこちら surge.sh
コマンド解説 dotstud.io
2. Introduction to Styling in Gatsby
グローバルスタイルを試す。
gatsby-browser.jsをルートディレクトリに作成し、任意の場所に作成したCSSファイルを読み込むと、全体のページにスタイルが適用されているのが確認できる。
gatsby-browser.js
import "./src/styles/global.css"
CSSモジュールを試す。
以下のディレクトリとファイルを作成。
src/components/container.js
import React from "react" import containerStyles from "./container.module.css" export default function Container({ children }) { return <div className={containerStyles.container}>{children}</div> }
src/components/container.module.css
.container { margin: 3rem auto; max-width: 600px; }
src/pages/about-css-modules.js
import React from "react" import Container from "../components/container" export default function About() { return ( <Container> <h1>About CSS Modules</h1> <p>CSS Modules are cool</p> </Container> ) }
/about-css-modulesを確認するとCSSが適用されていることが確認できる。
コンポーネントを作成し、そのコンポーネントにスタイルを設定する場合は以下の様にする。
src/pages/about-css-modules.js
import React from "react" import styles from "./about-css-modules.module.css" import Container from "../components/container" const User = props => ( <div className={styles.user}> <img src={props.avatar} className={styles.avatar} alt="" /> <div className={styles.description}> <h2 className={styles.username}>{props.username}</h2> <p className={styles.excerpt}>{props.excerpt}</p> </div> </div> ) export default function About() { return ( <Container> <h1>About CSS Modules</h1> <p>CSS Modules are cool</p> <User username="Jane Doe" avatar="https://s3.amazonaws.com/uifaces/faces/twitter/adellecharles/128.jpg" excerpt="I'm Jane Doe. Lorem ipsum dolor sit amet, consectetur adipisicing elit." /> <User username="Bob Smith" avatar="https://s3.amazonaws.com/uifaces/faces/twitter/vladarbatov/128.jpg" excerpt="I'm Bob Smith, a vertically aligned type of guy. Lorem ipsum dolor sit amet, consectetur adipisicing elit." /> </Container> ) }
3. Creating Nested Layout Components
Typography.jsの使用を試す。
npm install gatsby-plugin-typography react-typography typography typography-theme-fairy-gates
ルートディレクトリにgatsby-config.jsを作成。
gatsby-config.js
module.exports = { plugins: [ { resolve: `gatsby-plugin-typography`, options: { pathToConfigModule: `src/utils/typography`, }, }, ], }
gatsby-config.jsはプラグインやその他のサイト構成を追加するファイルとのこと。
詳細はここから。
www.gatsbyjs.com
Typography.jsの設定ファイルを作成
src/utils/typography.js
import Typography from "typography" import fairyGateTheme from "typography-theme-fairy-gates" const typography = new Typography(fairyGateTheme) export const { scale, rhythm, options } = typography export default typography
開発サーバーを再起動して、Chrome開発者ツールでheadタグ内を見てみると、
styleタグ内にTypography.jsのスタイルが記載されていることが確認できる。
また、ページ内のフォントが変更されていることが確認できる。
レイアウトコンポーネントについて。
以下の様に枠を作成して、呼び出すだけで統一したレイアウトにすることが出来る。
src/components/layout.js
import React from "react" export default function Layout({ children }) { return ( <div style={{ margin: `3rem auto`, maxWidth: 650, padding: `0 1rem` }}> {children} </div> ) }
src/pages/index.js
import React from "react" import Layout from "../components/layout" export default function Home() { return ( <Layout> <h1>Hi! I'm building a fake Gatsby site as part of a tutorial!</h1> <p> What do I like to do? Lots of course but definitely enjoy building websites. </p> </Layout> ); }
layout.jsにナビゲーションリンクなど付けると更に便利だよ、というような内容だった。
4. Data in Gatsby(GraphQL)
GraphQLの公式サイトにもチュートリアルがある。 www.howtographql.com
まず、サイトのタイトルがバラバラなので1つの場所にまとめましょうとのこと。
gatsby-config.jsのsiteMetadata>titleに任意のタイトル名を記載。
gatsby-config.js
module.exports = { siteMetadata: { title: `Title from siteMetadata`, }, plugins: [ `gatsby-plugin-emotion`, { resolve: `gatsby-plugin-typography`, options: { pathToConfigModule: `src/utils/typography`, }, }, ], }
以下の様にするとgatsby-config.jsで指定したtitleを使用できる。
開発サーバーを再起動すると確認できる。
gatsbyからgraphqlを読み込んで、const queryを作成している。
ページコンポーネントでのみ使用可能とのこと。
src/pages/about.js
import React from "react" import { graphql } from "gatsby" import Layout from "../components/layout" export default function About({ data }) { return ( <Layout> <h1>About {data.site.siteMetadata.title}</h1> <p> We're the only site running on your computer dedicated to showing the best photos and videos of pandas eating lots of food. </p> </Layout> ) } export const query = graphql` query { site { siteMetadata { title } } } `
StaticQueryの使用を試す。
StaticQueryはGatsbyのバージョン2で導入された新しいAPIで、ページ以外のコンポーネント(layout.jsコンポーネントなど)がGraphQLクエリを介してデータを取得できる、とのこと。
useStaticQueryを使用する。
src/components/layout.js
import React from "react" import { css } from "@emotion/core" import { useStaticQuery, Link, graphql } from "gatsby" import { rhythm } from "../utils/typography" export default function Layout({ children }) { const data = useStaticQuery( graphql` query { site { siteMetadata { title } } } ` ) return ( <div css={css` margin: 0 auto; max-width: 700px; padding: ${rhythm(2)}; padding-top: ${rhythm(1.5)}; `} > <Link to={`/`}> <h3 css={css` margin-bottom: ${rhythm(2)}; display: inline-block; font-style: normal; `} > {data.site.siteMetadata.title} </h3> </Link> <Link to={`/about/`} css={css` float: right; `} > About </Link> {children} </div> ) }
gatsby-config.jsのタイトルを変えてみるとすぐに反映することが確認できる。
5. Source Plugins
GraphQLとソースプラグインを使用してGatsbyサイトにデータを取り込む方法について試す。
名前がややこしいがGraphiQLというGraphQL統合開発環境があるらしい。 http://localhost:8000/___graphqlでアクセスできる。
使いたいデータ構造を予め作成できる感じか。
Gatsbyサイトのデータは、API、データベース、CMS、ローカルファイルなど、どこからでも取得できるとのこと。
例としてgatsby-source-filesystemを使用している。
npm install gatsby-source-filesystem
gatsby-config.jsにgatsby-source-filesystemを追加。
gatsby-config.js
module.exports = { siteMetadata: { title: `Pandas Eating Lots`, }, plugins: [ { resolve: `gatsby-source-filesystem`, options: { name: `src`, path: `${__dirname}/src/`, }, }, `gatsby-plugin-emotion`, { resolve: `gatsby-plugin-typography`, options: { pathToConfigModule: `src/utils/typography`, }, }, ], }
開発サーバーを再起動し、GraphiQLで確認すると、allFile、fileが追加されている。
GraphiQLでクエリにフィールドを追加して、実行すると結果が確認できる。
因みにExplolerボタンでコピペ用のコードが表示される。
試しに以下の様にして確認してみる。
(/pageで作成したファイル一覧が表示される)
src/pages/my-files.js
import React from "react" import { graphql } from "gatsby" import Layout from "../components/layout" export default function MyFiles({ data }) { console.log(data) return ( <Layout> <div> <h1>My Site's Files</h1> <table> <thead> <tr> <th>relativePath</th> <th>prettySize</th> <th>extension</th> <th>birthTime</th> </tr> </thead> <tbody> {data.allFile.edges.map(({ node }, index) => ( <tr key={index}> <td>{node.relativePath}</td> <td>{node.prettySize}</td> <td>{node.extension}</td> <td>{node.birthTime}</td> </tr> ))} </tbody> </table> </div> </Layout> ) } export const query = graphql` query { allFile { edges { node { relativePath prettySize extension birthTime(fromNow: true) } } } } `
6. Transformer plugins
ファイル内のデータをクエリすることを可能にするためにTransformer pluginsがあるとのこと。
例としてマークダウンファイルをHTMLに変換するgatsby-transformer-remarkを使用している。
ここでやっていることは、マークダウンファイルを読み込んでトップページにリストとして表示、という感じ。
マークダウンファイル作成
src/pages/sweet-pandas-eating-sweets.md
--- title: "Sweet Pandas Eating Sweets" date: "2017-08-10" --- Pandas are really sweet. Here's a video of a panda eating sweets. <iframe width="560" height="315" src="https://www.youtube.com/embed/4n0xNbfJLR8" frameborder="0" allowfullscreen></iframe>
npm install gatsby-transformer-remark
gatsby-config.js修正
gatsby-config.js
module.exports = { siteMetadata: { title: `Pandas Eating Lots`, }, plugins: [ { resolve: `gatsby-source-filesystem`, options: { name: `src`, path: `${__dirname}/src/`, }, }, `gatsby-transformer-remark`, `gatsby-plugin-emotion`, { resolve: `gatsby-plugin-typography`, options: { pathToConfigModule: `src/utils/typography`, }, }, ], }
開発サーバーを再起動すると、allMarkdownRemarkが追加されているので、マークダウンファイルに関するフィールドを使用することができる。
index.jsを修正して試す。
src/pages/index.js
import React from "react" import { graphql } from "gatsby" import { css } from "@emotion/core" import { rhythm } from "../utils/typography" import Layout from "../components/layout" export default function Home({ data }) { console.log(data) return ( <Layout> <div> <h1 css={css` display: inline-block; border-bottom: 1px solid; `} > Amazing Pandas Eating Things </h1> <h4>{data.allMarkdownRemark.totalCount} Posts</h4> {data.allMarkdownRemark.edges.map(({ node }) => ( <div key={node.id}> <h3 css={css` margin-bottom: ${rhythm(1 / 4)}; `} > {node.frontmatter.title}{" "} <span css={css` color: #bbb; `} > — {node.frontmatter.date} </span> </h3> <p>{node.excerpt}</p> </div> ))} </div> </Layout> ) } export const query = graphql` query { allMarkdownRemark { totalCount edges { node { id frontmatter { title date(formatString: "DD MMMM, YYYY") } excerpt } } } } `
新たなマークダウンファイルを作成すると、/indexの表示が変わることが確認できる。
またソートも可能で、GraphiQLでallMarkdownRemarkのソートフィールドを追加すれば、ソートが可能になる。
ここで便利さを改めて実感した。
7. Programmatically create pages from data
今度はマークダウンファイルごとのページを作成する。
そのためにはslugの作成が必要とのこと(https://www.gatsbyjs.com/tutorial/part-seven/の/tutorial/part-sevenの部分をslugと言う)。
Gatsby APIの内、2つのAPI(onCreateNode,createPages)を使用してマークダウンファイルからページを作成する。
Gatsby APIを使用するにはgatsby-node.jsを作成し、APIの名前を付けて関数をエクスポートする。
gatsby-node.js
exports.onCreateNode = ({ node }) => { console.log(`Node created of type "${node.internal.type}"`) }
開発サーバーを再起動すると、ターミナルにコンソールログが表示される。
進んでいくと、以下の様なファイル名を取得する処理の話になる。
MarkdownRemarkノードではファイル名が取得できないため、
getNode(node.parent)で親ノードを指定し、そこからrelativePathを取得しているとのこと。
gatsby-node.js
exports.onCreateNode = ({ node, getNode }) => { if (node.internal.type === `MarkdownRemark`) { const fileNode = getNode(node.parent) console.log(`\n`, fileNode.relativePath) } }
ただ、gatsby-source-filesystemで既に機能としてあるため以下の様な書き方で、slugの作成まで行うことが可能。
gatsby-node.js
const { createFilePath } = require(`gatsby-source-filesystem`) exports.onCreateNode = ({ node, getNode }) => { if (node.internal.type === `MarkdownRemark`) { console.log(createFilePath({ node, getNode, basePath: `pages` })) } }
次に、nodeの作成(API→onCreateNode)。
gatsby-node.js
const { createFilePath } = require(`gatsby-source-filesystem`) exports.onCreateNode = ({ node, getNode, actions }) => { const { createNodeField } = actions if (node.internal.type === `MarkdownRemark`) { const slug = createFilePath({ node, getNode, basePath: `pages` }) createNodeField({ node, name: `slug`, value: slug, }) } }
GraphiQLで確認すると、新たなslugが追加されている。
次に、ページ作成(API→createPages)。
gatsby-node.js
const path = require(`path`) const { createFilePath } = require(`gatsby-source-filesystem`) exports.onCreateNode = ({ node, getNode, actions }) => { const { createNodeField } = actions if (node.internal.type === `MarkdownRemark`) { const slug = createFilePath({ node, getNode, basePath: `pages` }) createNodeField({ node, name: `slug`, value: slug, }) } } exports.createPages = async ({ graphql, actions }) => { const { createPage } = actions const result = await graphql(` query { allMarkdownRemark { edges { node { fields { slug } } } } } `) result.data.allMarkdownRemark.edges.forEach(({ node }) => { createPage({ path: node.fields.slug, component: path.resolve(`./src/templates/blog-post.js`), context: { // Data passed to context is available // in page queries as GraphQL variables. slug: node.fields.slug, }, }) }) }
テンプレートを通してページとなるため、専用のファイルを作成。
src/templates/blog-post.js
import React from "react" import { graphql } from "gatsby" import Layout from "../components/layout" export default function BlogPost({ data }) { const post = data.markdownRemark return ( <Layout> <div> <h1>{post.frontmatter.title}</h1> <div dangerouslySetInnerHTML={{ __html: post.html }} /> </div> </Layout> ) } export const query = graphql` query($slug: String!) { markdownRemark(fields: { slug: { eq: $slug } }) { html frontmatter { title } } } `
開発サーバーを再起動し、マークダウンファイルの名前でアクセスするか、
適当にhttp://localhost:8000/sdfと存在しないパスを指定してから、対象のページへリンクすると確認できる。
最後にトップページにリンクを設定。
src/pages/index.js
import React from "react" import { css } from "@emotion/core" import { Link, graphql } from "gatsby" import { rhythm } from "../utils/typography" import Layout from "../components/layout" export default function Home({ data }) { return ( <Layout> <div> <h1 css={css` display: inline-block; border-bottom: 1px solid; `} > Amazing Pandas Eating Things </h1> <h4>{data.allMarkdownRemark.totalCount} Posts</h4> {data.allMarkdownRemark.edges.map(({ node }) => ( <div key={node.id}> <Link to={node.fields.slug} css={css` text-decoration: none; color: inherit; `} > <h3 css={css` margin-bottom: ${rhythm(1 / 4)}; `} > {node.frontmatter.title}{" "} <span css={css` color: #555; `} > — {node.frontmatter.date} </span> </h3> <p>{node.excerpt}</p> </Link> </div> ))} </div> </Layout> ) } export const query = graphql` query { allMarkdownRemark(sort: { fields: [frontmatter___date], order: DESC }) { totalCount edges { node { id frontmatter { title date(formatString: "DD MMMM, YYYY") } fields { slug } excerpt } } } } `
8. Preparing a Site to Go Live
Lighthouseでサイト診断。
# ビルド gatsby build # ローカルで本番サイトを確認するためのコマンド gatsby serve
こんな感じで診断結果が表示される。
ここでチュートリアルは大体完了。
9.GAEへデプロイ
GCPは完全に初心者だったので、まず環境構築からだったが、デプロイ完了までそこまで時間はかからなかった(1,2時間くらい?)。
以下の記事を元に、まずデプロイするまでざっくりやってみる。
公式サイトとこちらの記事を参考。 cloud.google.com shotat.hateblo.jp
gcloudコマンドのインストール等も公式とこちらの記事を参考にした。 qiita.com
初期設定等が完了したら、デプロイするアプリのルートディレクトリにapp.yamlを作成。
runtime: php55 api_version: 1 handlers: # file with extensions (e.g. .html) - url: /(.*\..*) static_files: public/\1 upload: public/(.*) - url: /(.*)/ static_files: public/\1/index.html upload: public/(.*)/index.html - url: / static_files: public/index.html upload: public/index.html - url: /(.*) static_files: public/\1/index.html upload: public/(.*)/index.html skip_files: - node_modules/ - src/ - \.cache/ - package\.json - yarn\.lock
後はコマンドを打つだけ。
# デプロイ gcloud app deploy # アプリの表示 gcloud app browse
まとめ
次は簡単なwebアプリを作ってみる。
GraphQL、GCP(GAE)も今回初めて触ったので、まとめて記事にする。
Next.jsもあまり明るくないので、実際に触ってみてGatsbyと比較してみる。