概要
Gatsbyは、React.jsを使ったStatic Site Generator(SSG、静的サイトジェネレーター)で、とても早いウェブサイトを作れる技術です。静的なサイトだけでなく、CSRやSSRサイトも作れますが、この記事では静的サイトに集中しておきたいと思います。Gatsbyが使用するGraphQLについても扱います。
Google Lighthouse Score
対象読者
- Gatsbyがどう動くか知りたい方
- Reactの基本的知識がある方
- ブログをScratchで作りたい方
目標
- Gatsbyの構造を知ることによって、色んなライブラリーを応用できるようになる
注意事項
- 完成品の基本的構造にふれるだけなので、上記の例と全く同じものが作られる記事ではありません。
- Gatsbyのことがよくわかってて、開発時間を下げたい方はGatsby Starterを使用することをおすすめします。
npm i -g gatsby-cli
gatsby new [project-name] [starter]
目次
- インストールと環境設定
- レイアウトを作る
- GraphQLでQueryを使う
- Markdownファイルを扱う
- Postページを生成する
- タグを生成する
- Paginationを行う
- イメージを使う
- スタイリング
- SEO、PWA設定
- デプロイする
- 最後に
インストールと環境設定
GatsbyをScratchから作るので、空のfolderからスタートします。
Gatsbyには大きく3種類のpluginが存在します。
-
Transformer Plugin
- JSONやMarkdownといった、そのままでは使えないデータを、GraphQLでqueryできる形に変換してくれます。ここでは、Markdownをhtmlに変換してくれる
gatsby-transformer-remark
と、MDXをhtmlに変換してくれるgatsby-plugin-mdx
を使います。
- JSONやMarkdownといった、そのままでは使えないデータを、GraphQLでqueryできる形に変換してくれます。ここでは、Markdownをhtmlに変換してくれる
-
Functional Plugin
- TypeScriptサポートやPWA設定など、機能的な要素を追加してくれます。ここではリンクをリロードなしに開いてくれることでSPAの感覚を与えてくれる
gatsby-plugin-catch-links
等を使います。
- TypeScriptサポートやPWA設定など、機能的な要素を追加してくれます。ここではリンクをリロードなしに開いてくれることでSPAの感覚を与えてくれる
-
Source Plugin
- Gatsbyのデータシステムの核心である
nodes
を作ってくれます。例えばgatsby-source-filesystem
はディスクからファイルをロードして、Transformer pluginがデータを変換できるようにします。ここでは使用しませんが、wordpressやcontentfulなどのCMSからnodes
を作成することもできます(gatsby-source-wordpress
,gatsby-source-contentful
)
- Gatsbyのデータシステムの核心である
まず、npm(またはyarn)を使ってdependenciesをインストールしていきます。
mkdir [project-name]
cd [project-name]
npm init -y
npm i react react-dom gatsby gatsby-source-filesystem gatsby-transformer-remark gatsby-plugin-catch-links
そうしたら、gatsby-config.js
を作成し、
自分が作りたいサイトのmeta情報を書き込みましょう。
module.exports = {
siteMetadata: {
title:'My Wonderful Website',
description: 'Welcome to your brilliant website.',
author: 'Super Cool Developer'
},
}
レイアウトを作る
レイアウトは私達が作るblogの土台になる部分です。
例えばすべてのページに共通して現れるHeader, Footer, Sidebar等が該当します。
import React from 'react'
export default ({ children }) => (
<div>
<h3>My Wonderful Website Layout</h3>
{children}
</div>
)
そしてホームの画面とAboutの画面を作っていきます。
Gatsbyのsrc/pagesの中のファイルは、各々一つのページとして作成されます。
例えば、src/pages/about.jsx
というファイルがあると、/about
が作られます。
import React from 'react'
import Layout from '../layouts'
export default () => (
<Layout>
<h1>My Wonderful Website Home Page</h1>
<p>This is the home page</p>
</Layout>
)
import React from 'react'
import Layout from '../layouts'
export default () => (
<Layout>
<h1>My Wonderful Website About</h1>
<p>This is the about page.</p>
</Layout>
)
ここで、gatsby develop
コマンドを実行してみましょう。
http://localhost:8000/
これだと寂しいので、先程作ったLayoutにリンクを足してみましょう。
import React from 'react'
import { Link } from 'gatsby'
export default ({ children }) => (
<div>
<Link to="/">
<h3>My Layout</h3>
</Link>
<Link to="/about">
About
</Link>
{children}
</div>
)
Aboutページにも移動できました。
試しにhttp://localhost:8000/not-found
など、存在しないページに移動すると、
このようにdevelopモードでは存在するページの一覧を表示してくれます。
GraphQLでqueryを使う
Gatsbyはデータをfetchする際にGraphQLを使用します。
GraphQLはデータにアクセスするためのQuery言語です。他のQuery言語の例としてはSQLがあります。
GraphQLは自分がほしいデータのみをもらうことができます。
GraphiQLを使う
まずはGraphiQLというツールを使ってqueryを実行してみましょう。
http://localhost:8000/__graphql
に移動してみてください。
GraphiQLのplaygroundが出てきます。
Explorer
をクリックしたら、fetchできるqueryの情報が出てきます。
Docs
をクリックしたら、queryに必要なInputの情報や返ってくるデータの情報が見れます。
ここで私達が先程設定したサイトのデータをfetchしてみましょう。
{
site {
siteMetadata {
title
description
author
}
}
}
そうしたら、下の結果が見れるはずです。
{
"data": {
"site": {
"siteMetadata": {
"title": "My Wonderful Website",
"description": "Welcome to your brilliant website.",
"author": "Super Cool Developer"
}
}
}
}
コンポーネント内でGraphQLを使う
現在homeとaboutには各々サイトのタイトル、My Wonderful Website
が入ってますが、
この書き方だとタイトルを修正したい時、毎回両方のページを修正する必要があります。
なので、gatsby-config.js
に書かれている情報をそのまま持ってきて使えるようにしましょう。
Page Query
src/pages
内のコンポーネントでは、以下のようにqueryを実行します。
graphql`
{クエリ}
`
違和感を感じるかもしれませんが、ちゃんとしたJavaScriptです。
import React from 'react'
import { graphql } from 'gatsby'
import Layout from '../layouts'
export default ({ data }) => (
<Layout>
<h1>{data.site.siteMetadata.title} About</h1>
<p>This is the about page.</p>
</Layout>
)
export const query = graphql`
query AboutQuery {
site {
siteMetadata {
title
}
}
}
`
/about
ページに行くと、titleがしっかり入ってるのがわかります。
StaticQuery
ページではないコンポーネントでは、StaticQueryを使用します。
先程作成したLayoutで使ってみましょう。
まずgraphql
とStaticQuery
をgatsby
からインポートします。
StaticQuery
はquery
とrender
プロパティを受け取ります。
render
はpropとしてqueryを実行した結果のdata
を返します。
import React from 'react'
import { Link, graphql, StaticQuery } from 'gatsby'
export default ({ children }) => (
<StaticQuery
query={graphql`
query {
site {
siteMetadata {
title
}
}
}
`}
render={data => (
<div>
<Link to="/">
<h3>{data.site.siteMetadata.title} Layout</h3>
</Link>
<Link to="/about">
About
</Link>
{children}
</div>
)}
/>
)
Markdownファイルを扱う
形はできてきたので、コンテンツを作ってみましょう。
ルートフォルダーにposts
フォルダーを作成し、その中に[ポスト名]
のフォルダーを作成してください。
その中にindex.md
ファイルを作ります。
私はポスト名をpost-one
にしました。
---
path: '/post-one'
date: '2019-08-18'
title: 'My First Post'
tags: ['gatsby', 'qiita']
---
This is my beautiful first post.
ファイルを読み込む(pluginの設定)
マークダウンファイルをGatsbyが読み込むためにはプラグインが必要です。
一番最初にインストールしたプラグインをgatsby-config.js
に書き込み、設定をしていきます。
module.exports = {
siteMetadata: {
title: 'My Wonderful Website',
description: 'Welcome to your brilliant website.',
author: 'Cool Developer'
},
plugins: [
'gatsby-plugin-catch-links',
'gatsby-transformer-remark',
{
resolve: 'gatsby-source-filesystem',
options: {
name: 'posts',
path: `${__dirname}/posts`
}
}
]
}
pathは自分の*.md
ファイルが存在するところである必要があります。
準備はできたのでGraphiQLに戻ります。
GraphiQLで確認する
GraphiQL-Explorer
から、allMarkdownRemark
を選び、すべてのフィールドを確認してみてください。
edges
はファイルパスを意味し、その中にあるnode
は先程作成したmdファイルを意味します。
以下のqueryを実行してみましょう。
{
allMarkdownRemark {
edges {
node {
frontmatter
}
}
}
}
実行結果
{
"data": {
"allMarkdownRemark": {
"edges": [
{
"node": {
"frontmatter": {
"title": "My First Post",
"path": "/post-one",
"date": "2019-08-18"
}
}
}
]
}
}
}
frontmatter
の中に先程作成したmdファイルの中身が入ってくるのがわかります。
gatsby-transformer-remark
プラグインが働いてくれました。
このデータを自由に使ってポストページを作っていきます。
ポストリストをHomeに表示する
余裕のある方は、このセクションは自分の力でやってみてください!
page-queryを使用してhomeにデータが渡ってきたら、それをReact方式に表示するだけです。
node
の中にexcerpt
は、コンテンツの内容を前の部分だけ切り取って表示してくれます。
私はpruneLength
を使って、先頭100文字だけを表示するようにします。(defaultは140)
import React from 'react'
import { graphql } from 'gatsby'
import Layout from '../layouts'
export default ({ data }) => {
const { edges } = data.allMarkdownRemark
return (
<Layout>
<h1>Gatsby Tutorial Home Page</h1>
{edges.map(({ node }) => (
<div key={node.id}>
<h3>{node.frontmatter.title}</h3>
<p>{node.frontmatter.date}</p>
<p>{node.excerpt}</p>
</div>
))}
</Layout>
)}
export const query = graphql`
query {
allMarkdownRemark {
edges {
node {
id
excerpt(pruneLength: 100)
frontmatter {
title
date
}
}
}
}
}
`
ポストがちゃんと表示されました!
ソート
allMarkdownRemark
はsort
・filter
・skip
・limit
の4つのプロパティを受け取れます。
skip
とlimit
はPaginationの時に有用な項目ですが、ここではsort
とfilter
を扱います。
sort
ではorder
とfields
を設定できます。
新しいポストが上に来てほしいので、order
はDESC
にし、
fieldsはformatmatter___date
に設定します。
また、dateも日本語表記にしたいので、formatString
をYYYY年MM月DD日
に指定しましょう。
{
allMarkdownRemark(sort: { order: DESC, fields: [frontmatter___date] }) {
edges {
node {
id
excerpt
frontmatter {
title
date(formatString: "YYYY年MM月DD日")
}
}
}
}
}
フィルタリング
ブログを書くと下書き状態のポストもあるかと思いますが、
今のままではすべてのポストが公開されてしまいます。
なので作成したポストにstatus
を追加しましょう。
---
path: '/post-one'
date: '2019-08-18'
title: 'My First Post'
tags: ['gatsby', 'qiita']
status: 'published'
---
This is my beautiful first post.
---
path: '/post-two'
date: '2019-08-19'
title: 'My Second Post'
tags: ['gatsby', 'qiita']
status: 'draft'
---
This is my magnificent second post.
では、status
がpublished
のポストのみをfetchするqueryを書きましょう。
{
allMarkdownRemark(
filter: {
frontmatter: {
status: { eq: "published" }
}
}) {
edges {
node {
id
excerpt
frontmatter {
title
date
}
}
}
}
}
これでpublished
状態のポストしか見えないようになりました。
Postページを生成する
ポストのリストが表示されたので、中身が見れるように各々のページを生成していきます。
まず、入り口を作るために、Link
を設定しましょう。
import { graphql, Link } from 'gatsby'
...
<Link to={node.frontmatter.path}>
<h3>{node.frontmatter.title}</h3>
</Link>
...
queryにもpath
を追加します。
...
frontmatter {
title
date(formatString: "YYYY年MM月DD日")
path
}
...
ポストページを生成する処理を書くgatsby-node.js
をルートディレクトリに作成します。
また、ポストの詳細を表示するテンプレート、src/templates/post.jsx
を作成します。
import React from 'react'
const Post = (props) => {
console.log('後で消す', props)
return (
<div>
This is a post
</div>
)
}
export default Post
次にgatsby-node.js
ではGatsbyのAPIの一つであるcreatePages
を使用します。
まずは、Pageを作成する関数を書いていきましょう。
path.resolve
を使い、先程作成したpostのテンプレートを取得、
queryを実行してGatsbyが用意してくれるaction関数のcreatePage
を実行していきます。
const path = require('path')
exports.createPages = (({graphql, actions}) => {
const { createPage } = actions
return new Promise((resolve, reject) => {
const postTemplate = path.resolve('src/templates/post.jsx')
resolve(
graphql(
`
query {
allMarkdownRemark {
edges {
node {
frontmatter {
path
title
}
}
}
}
}
`
).then(result => {
if (result.errors) {
return Promise.reject(result.errors)
}
const posts = result.data.allMarkdownRemark.edges;
posts.forEach(({node}) => {
const path = node.frontmatter.path
createPage({
path,
component: postTemplate,
context: {
pathSlug: path
}
})
})
})
)
})
})
長い関数ですね。お疲れさまです!
では、サーバーを再起動して、postページに行ってみてください。
template通りのページが作成されたと思います。
デベロッパーコンソールを開いて、console.log
を確認してみてください。
pageContext
が渡ってきてるのがわかります。
ここにはcreatePage
のcontextに設定したデータが入ってきます。
従って、pathSlug
が入っているはずです。
このデータを活用してページを作成するために、template側を修正する必要があります。
import React from 'react'
import { graphql } from 'gatsby'
import Layout from '../layouts'
const Post = ({ data }) => {
const post = data.markdownRemark
const title = post.frontmatter.title
const date = post.frontmatter.date
const html = post.html
return (
<Layout>
<h1>{title}</h1>
<p>{date}</p>
<div dangerouslySetInnerHTML={{ __html: html }} />
</Layout>
)
}
export const query = graphql`
query($pathSlug: String!) {
markdownRemark(frontmatter: { path: {eq: $pathSlug} }) {
html
frontmatter {
date
title
}
}
}
`
export default Post
markdownRemark
queryを使用し、そこに先程gatsby-node.js
で渡したpathSlug
を入れてます。
pageContextに設定したプロパティをそのままpage-queryに入れてます。
これでPostの詳細ページの完成です!
タグを生成する
次に、タグの一覧を集めたページと、
タグをクリックした時に同じタグのポストのみ表示させるページを作りたいと思います。
import React from 'react'
const Tags = props => {
return (
<div>
Tags
</div>
)
}
export default Tags
次に、前のセクションと同様、タグのテンプレートを作成します。
import React from 'react'
const Tag = props => {
return (
<div>
Tag
</div>
)
}
export default Tag
gatsby-node.js
に戻り、タグ別にページを作る処理を書きましょう。
すべてのポストのタグを集め、タグ別にポストを分類し、
createPage
のcontextにタグの情報を渡して上げればOKです。
const path = require('path')
exports.createPages = (({graphql, actions}) => {
const { createPage } = actions
return new Promise((resolve, reject) => {
const postTemplate = path.resolve('src/templates/post.jsx')
const tagPage = path.resolve('src/pages/tags.jsx');
const tagPosts = path.resolve('src/templates/tag.jsx');
resolve(
graphql(
`
query {
allMarkdownRemark {
edges {
node {
frontmatter {
path
title
tags
}
}
}
}
}
`
).then(result => {
if (result.errors) {
return Promise.reject(result.errors)
}
const posts = result.data.allMarkdownRemark.edges;
const postsByTag = {};
posts.forEach(({ node }) => {
if (node.frontmatter.tags) {
node.frontmatter.tags.forEach(tag => {
if (!postsByTag[tag]) {
postsByTag[tag] = [];
}
postsByTag[tag].push(node);
});
}
});
const tags = Object.keys(postsByTag);
tags.forEach(tagName => {
const posts = postsByTag[tagName]
createPage({
path: `/tags/${tagName}`,
component: tagPosts,
context: {
posts,
tagName
}
})
})
createPage({
path: '/tags',
component: tagPage,
context: {
tags: tags.sort(),
},
});
posts.forEach(({node}) => {
const path = node.frontmatter.path
createPage({
path,
component: postTemplate,
context: {
pathSlug: path
}
})
})
})
)
})
})
gatsby-node.js
の修正が終わったので、サーバーを再起動して、
contextを受け取るコンポーネント側も修正しましょう。
import React from 'react'
import { Link } from 'gatsby'
const Tags = ({pageContext}) => {
const { tags } = pageContext
return (
<div>
<ul>
{tags.map((tagName, index) => {
return (
<li key={index}>
<Link to={`/tags/${tagName}`}>
{tagName}
</Link>
</li>
)
})}
</ul>
</div>
)
}
export default Tags
import React from 'react'
import { Link } from 'gatsby'
const Tag = ({ pageContext }) => {
const { posts, tagName } = pageContext
return (
<div>
<div>
Posts about {`${tagName}`}
</div>
<div>
<ul>
{posts.map((post, index) => {
return (
<li key={index}>
<Link to={post.frontmatter.path}>
{post.frontmatter.title}
</Link>
</li>
)
})}
</ul>
</div>
</div>
)
}
export default Tag
せっかくだしポストのページにもタグを追加しましょう。
タグリストのコンポーネントを作成します。
import React from 'react'
import { Link } from 'gatsby'
const TagList = ({ tags }) => {
return (
<div>
{tags.map(tag =>
<Link key={tag} to={`/tags/${tag}`}>
{tag}
</Link>
)}
</div>
)
}
export default TagList
...
<TagList tags={post.frontmatter.tags || []} />
...
これでタグができました!
前の記事、次の記事を実装する
前の記事、次の記事のリンクをポストのページにおきたいです。
そのために、gastby-node.js
でpostページ生成のロジックをいじってみましょう。
...
posts.forEach(({ node }, index) => {
const path = node.frontmatter.path
const prev = index === 0 ? null : posts[index - 1].node;
const next = index === posts.length - 1 ? null : posts[index + 1].node;
createPage({
path,
component: postTemplate,
context: {
pathSlug: path,
prev,
next,
}
});
});
...
インデックスを使って前、次を判断してるので、まずはデータをソートする必要がありそうです。
前のセクションでやった内容の復習ですね。しかし今回は降順ではなく昇順でソートします。
...
query {
allMarkdownRemark (
sort: {order: ASC, fields: [frontmatter___date]}
) {
edges {
node {
frontmatter {
path
title
date
}
}
}
}
}
...
これでprev
とnext
の情報がpageContextで渡ってきますので、src/pages/post.jsx
を修正しましょう。
import React from 'react'
import { graphql, Link } from 'gatsby'
import Layout from '../layouts'
import TagList from '../components/TagList';
const Post = ({ data, pageContext }) => {
const post = data.markdownRemark
const title = post.frontmatter.title
const date = post.frontmatter.date
const html = post.html
const { prev, next } = pageContext
return (
<Layout>
<h1>{title}</h1>
<p>{date}</p>
<div dangerouslySetInnerHTML={{ __html: html }} />
<TagList tags={post.frontmatter.tags || []} />
{next &&
<Link to={next.frontmatter.path}>
Next
</Link>
}
{prev &&
<Link to={prev.frontmatter.path}>
Previous
</Link>
}
</Layout>
)
}
これでシンプルなページ設定は終わりです。
イメージを使う
今回はイメージを読み込めるようにします。
いろんな方法がありますが、ここでは普通のJSインポートと、
gatsbyのpluginを使った2つの方法を紹介します。
JSインポート
まずは適当なロゴファイルをプロジェクトフォルダーに入れましょう。
私はsrc/images/logo.png
という名前にしました。
...
import logo from '../images/logo.png'
...
<img src={logo} alt="logo" />
...
import
文を使ってインポートしたイメージをそのままimg
タグのsrc
に渡せばいいです。
Gatsbyのすごいところは、パフォーマンスを重視した設定を自動でやってくれることです。
10KB以下のファイルはuriで渡してくれますが、
10KBを超えるファイルはstaticフォルダーにバンドルしてくれます。
Pluginを使う
とても高いパフォーマンスでイメージをさばいてくれるpluginを使ってみましょう。
まずは、プラグインとgatsby-image
をインストールしましょう。
npm i gatsby-transformer-sharp gatsby-plugin-sharp gatsby-remark-images gatsby-image
gatsby-config.js
でプラグインを設定します。
今回はマークダウンの中のイメージをプロセスしてくれるgatsby-remark-images
を、
gatsby-transformer-remark
のoptionsに設定します。
更に、maxWidth
とquality
、linkImagesToOriginal
を書いていきます。
他の詳しいoptionは、
(https://www.gatsbyjs.org/packages/gatsby-remark-images/?=remark)
を参考にしてください。
plugins: [
'gatsby-plugin-catch-links',
'gatsby-transformer-sharp',
'gatsby-plugin-sharp',
{
resolve: 'gatsby-source-filesystem',
options: {
name: 'posts',
path: `${__dirname}/posts`
}
},
{
resolve: 'gatsby-transformer-remark',
options: {
plugins: [
{
resolve: 'gatsby-remark-images',
options: {
maxWidth: 690,
quality: 90,
linkImagesToOriginal: true,
}
}
]
}
}]
次はポストのカバーイメージを設定しましょう。
ポストの同じフォルダーにイメージを準備しておき、coverイメージを追加します。
...
date: '2019-08-18',
title: 'My First Post',
cover: './image.jpg'
...
GraphiQLのページですべてのマークダウン内のイメージを読み込んでみましょう。
{
allMarkdownRemark {
edges {
node {
id
excerpt(pruneLength: 100)
frontmatter {
title
date(formatString: "YYYY年MM月DD日")
path
cover {
childImageSharp {
fluid(maxWidth: 1000, quality: 90) {
src
}
fixed(width: 650) {
src
}
}
}
}
}
}
}
}
childImageSharp
の中のfixed
とfluid
を見てください。
fixed
は指定されたwidth
とheight
でイメージを返してくれ、
fluid
はresponsiveにイメージを返してくれます。
Gatsbyはユーザーの使っている端末に合わせて最適なサイズのイメージを自動的に使ってくれます。
lazy-loadingなどもやってくれるので、設定無しですごくパフォーマンスの良いサイトを作れます。
残念ながらGraphiQLのエラーでfragment
を使えません。
fragment
は、GraphQLのqueryを使い回すために、予め設定しておいたものです。
gatsby-image
には、予め定められたfragment
がたくさんあるので、そこから選んで使いましょう。
https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-image#fragments
では実際のコンポーネントで使ってみます。
import React from 'react'
import { graphql, Link } from 'gatsby'
import Img from 'gatsby-image'
import Layout from '../layouts'
export default ({ data }) => {
const { edges } = data.allMarkdownRemark
return (
<Layout>
<h1>Gatsby Tutorial Home Page</h1>
{edges.map(({ node }) => (
<div key={node.id}>
<Img
fluid={node.frontmatter.cover.childImageSharp.fluid}
alt={node.frontmatter.title}
/>
<Link to={node.frontmatter.path}>
<h3>{node.frontmatter.title}</h3>
</Link>
<p>{node.frontmatter.date}</p>
<p>{node.excerpt}</p>
</div>
))}
</Layout>
)}
export const query = graphql`
query {
allMarkdownRemark {
edges {
node {
id
excerpt(pruneLength: 100)
frontmatter {
title
date(formatString: "YYYY年MM月DD日")
path
cover {
childImageSharp {
fluid(maxWidth: 1000, quality: 90) {
...GatsbyImageSharpFluid_withWebp_tracedSVG
}
}
}
}
}
}
}
}
`
homeのページで、すべてのマークダウンのカバーイメージを表示するようにしました。
gatsby-image
をImg
としてインポートして、その中のfixed
やfluid
プロパティに、イメージのデータを渡してあげます。
src/templates/post.jsx
でも、同じくカバーイメージを追加しましょう。
import React from 'react'
import { graphql, Link } from 'gatsby'
import Img from 'gatsby-image'
import Layout from '../layouts'
import TagList from '../components/TagList';
const Post = ({ data, pageContext }) => {
const post = data.markdownRemark
const title = post.frontmatter.title
const date = post.frontmatter.date
const html = post.html
const { prev, next } = pageContext
return (
<Layout>
<Img
fluid={post.frontmatter.cover.childImageSharp.fluid}
alt={node.frontmatter.title}
/>
<h1>{title}</h1>
<p>{date}</p>
<div dangerouslySetInnerHTML={{ __html: html }} />
<TagList tags={post.frontmatter.tags || []} />
{next &&
<Link to={next.frontmatter.path}>
Next
</Link>
}
{prev &&
<Link to={prev.frontmatter.path}>
Previous
</Link>
}
</Layout>
)
}
export const query = graphql`
query($pathSlug: String!) {
markdownRemark(frontmatter: { path: {eq: $pathSlug} }) {
html
frontmatter {
date
title
tags
cover {
childImageSharp {
fluid(maxWidth: 1920, quality: 90) {
...GatsbyImageSharpFluid_withWebp
}
}
}
}
}
}
`
これで、イメージを表示できるようになりました!
スタイリング
ここまでお疲れさまです!
ブログがちゃんと動くようになりましたね。
しかし、見栄えが全然よくないので、スタイリングを追加していきます。
Reactでのスタイリングはいろんな方法があります。
ここではreact-emotion
というライブラリーを使っていきたいと思います。
styled-components
と似てるので、入れ替えても問題ないかと思います。
まず、関連ライブラリーをインストールしましょう。
npm i emotion @emotion/core @emotion/styled emotion-theming gatsby-plugin-emotion
gatsby-config.js
にgatsby-plugin-emotion
を追加しておきましょう。
Global、ThemeProvider
src
フォルダーの外にconfig/theme.js
を作成します。
theme.js
には、使いまわしたいcssを変数として保存しておきます。
それをThemeProvider
に渡すことで、いろんなコンポーネントで使い回すことができます。
const colors = {
black: {
base: '#333438',
light: '#4b4e57',
lighter: '#696d77',
blue: '#2e3246',
}
}
const transition = {
easeInOutCubic: 'cubic-bezier(0.645, 0.045, 0.355, 1)',
easeOutBack: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)',
duration: '0.4s',
}
const theme = {
colors,
transition,
}
export default theme
ThemeProvider
はReactのContext APIを使ってますが、DarkModeなどの設定を書くのも便利です。
詳しい方法は下の記事に書いてあったので、気になる方は試してみてください。
https://morimo7.hatenablog.com/entry/2019/07/13/155418
theme.js
を書いたので、実際にThemeProvider
を使ってみます。
すべてのページで共有するlayout
に変更を加えましょう。
中身は、src/components/NavBar.jsx
に切り出します。
import React from 'react'
import { ThemeProvider } from 'emotion-theming'
import { Global, css, } from '@emotion/core'
import theme from '../../config/theme'
const reset = css`
*, *:before, *:after {
box-sizing: inherit;
}
html, body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
`
export default ({ children }) => (
<ThemeProvider theme={theme}>
<>
<Global styles={reset}/>
{children}
</>
</ThemeProvider>
)
Global
では全体に適用したいスタイルを書きました。
ここではcssのリセットですね。
import React from 'react'
import { Link } from 'gatsby'
import styled from '@emotion/styled'
import logo from '../images/logo.png'
const StyledLink = styled(Link)`
display: flex;
font-weight: 700;
align-items: center;
`
const Nav = styled.nav`
display: flex;
justify-content: flex-end;
font-weight: 500;
font-size: 1.25rem;
align-items: center;
a {
color: ${props => props.theme.colors.black.base};
margin-left: 2rem;
transition: all ${props => props.theme.transitions};
&:hover {
color: ${props => props.theme.colors.black.lighter};
}
}
`
const NavBar = () => (
<>
<StyledLink to="/">
<img src={logo} alt='Gatsby Logo' />
</StyledLink>
<Nav>
<Link to='/'>Home</Link>
<Link to='/about'>About</Link>
</Nav>
</>
)
export default NavBar
styled
を使ってスタイリングしていきます。
emotion
のThemeProvider
を使えば、props
からtheme
にアクセスすることができます。
一例としてemotion
を使う方法を紹介しましたが、
他に自分の気に入ってる方法があればご自由に使用してください。
この記事はCSSの記事ではないので、スタイリングに関しては以上になります。
SEO、PWA設定
ブログは、コンテンツをより多くの人に露出するために、SEO対策がとても大事です。
このセクションでは、Google LighthouseのSEOスコアを100にしていきます。
manifest
まずはPWAサポートのためにmanifestを追加します。
npm i react-helmet gatsby-plugin-react-helmet gatsby-plugin-manifest gatsby-plugin-sitemap gatsby-plugin-offline
今まではgatsby-config.js
にサイトの情報を書いておきましたが、config/site.js
に切り出します。
module.exports = {
pathPrefix: '/',
title: 'My Wonderful Website', // タイトル
titleAlt: 'My Wonderful Website', // JSONLDのためのタイトル
description: 'Welcome to my brilliant website.',
url: 'https://[].netlify.com', // スラッシュなしのサイトURL
siteURL: 'https://[].netlify.com/', // スラッシュありのサイトURL
siteLanguage: 'ja', // HTMLの言語(ここでは日本語)
logo: 'src/images/logo.png',
banner: 'src/images/banner.png',
favicon: 'src/images/favicon.png', // ファビコン
shortName: 'CoolSite', // サイトの略称、12文字以下
author: 'so99ynoodles', // schemaORGJSONLDの作成者
themeColor: '#3e7bf2',
backgroundColor: '#d3e0ff',
twitter: '@so996ynoodles', // TwitterのID
};
const config = require('./config/site');
module.exports = {
siteMetadata: {
...config
},
...
また、インストールしたプラグインの設定も書きましょう。
...
'gatsby-plugin-sitemap',
{
resolve: 'gatsby-plugin-manifest',
options: {
name: config.title,
short_name: config.shortName,
description: config.description,
start_url: config.pathPrefix,
background_color: config.backgroundColor,
theme_color: config.themeColor,
display: 'standalone',
icon: config.favicon,
},
},
'gatsby-plugin-offline'
]
...
gatsby-plugin-sitemap
を書いたあとに、gatsby-plugin-manifest
を書きます。
gatsby-plugin-offline
をその後に書くことで、manifestファイルがcacheされます。
SEO
src/components/SEO.jsx
を作成します。
まずは先程作成したサイトの情報をとってきて、react-helmet
を使って記述していきます。
react-helmet
はdocumentのheadをいじれるライブラリーです。
Helmetの情報は自動的にstaticなHTMLとして変換されます。
JavaScriptのコンテンツは一般的にSEOに向かないので助かりますね。
// src/components/SEO.jsx
import React, { Component } from 'react'
import Helmet from 'react-helmet'
import PropTypes from 'prop-types'
import { StaticQuery, graphql } from 'gatsby'
const SEO = ({ title, desc, banner, pathname, article }) => (
<StaticQuery
query={query}
render={({
site: {
buildTime,
siteMetadata: {
defaultTitle,
titleAlt,
shortName,
author,
siteLanguage,
logo,
siteUrl,
pathPrefix,
defaultDescription,
defaultBanner,
twitter,
},
},
}) => {
const seo = {
title: title || defaultTitle,
description: defaultDescription || desc,
image: `${siteUrl}${banner || defaultBanner}`,
url: `${siteUrl}${pathname || '/'}`,
};
}}
/>
);
export default SEO
SEO.propTypes = {
title: PropTypes.string,
desc: PropTypes.string,
banner: PropTypes.string,
pathname: PropTypes.string,
article: PropTypes.bool,
};
SEO.defaultProps = {
title: null,
desc: null,
banner: null,
pathname: null,
article: false,
};
const query = graphql`
query SEO {
site {
buildTime(formatString: "YYYY年MM月DD日")
siteMetadata {
defaultTitle: title
titleAlt
shortName
author
siteLanguage
logo
siteUrl: url
pathPrefix
defaultDescription: description
defaultBanner: banner
twitter
}
}
}
`;
次にschema.orgを使ってJSON-LDを書いていきます。
JSON-LDはGoogle推奨、リッチスニペットを表示させる構造的マークアップです。
詳しい内容はQiitaにいい記事があったので、参考にしてください。
https://qiita.com/narumana/items/b66969b80cce848b2ddf
...
const seo = {
title: title || defaultTitle,
description: defaultDescription || desc,
image: `${siteUrl}${banner || defaultBanner}`,
url: `${siteUrl}${pathname || '/'}`,
};
const realPrefix = pathPrefix === '/' ? '' : pathPrefix;
let schemaOrgJSONLD = [
{
'@context': 'http://schema.org',
'@type': 'WebSite',
'@id': siteUrl,
url: siteUrl,
name: defaultTitle,
alternateName: titleAlt || '',
},
];
if (article) {
schemaOrgJSONLD = [
{
'@context': 'http://schema.org',
'@type': 'BlogPosting',
'@id': seo.url,
url: seo.url,
name: title,
alternateName: titleAlt || '',
headline: title,
image: {
'@type': 'ImageObject',
url: seo.image,
},
description: seo.description,
datePublished: buildTime,
dateModified: buildTime,
author: {
'@type': 'Person',
name: author,
},
publisher: {
'@type': 'Organization',
name: author,
logo: {
'@type': 'ImageObject',
url: siteUrl + realPrefix + logo,
},
},
isPartOf: siteUrl,
mainEntityOfPage: {
'@type': 'WebSite',
'@id': siteUrl,
},
},
];
}
}}
/>
);
...
最後に、書いた情報をHelmet
に埋め込みます。
FacebookのためのOpenGraphブロックと、twitterブロックも用意しました。
...
'@id': siteUrl,
},
},
];
}
return (
<>
<Helmet title={seo.title}>
<html lang={siteLanguage} />
<meta name="description" content={seo.description} />
<meta name="image" content={seo.image} />
<meta name="apple-mobile-web-app-title" content={shortName} />
<meta name="application-name" content={shortName} />
<script type="application/ld+json">{JSON.stringify(schemaOrgJSONLD)}</script>
{/* OpenGraph */}
<meta property="og:url" content={seo.url} />
<meta property="og:type" content={article ? 'article' : null} />
<meta property="og:title" content={seo.title} />
<meta property="og:description" content={seo.description} />
<meta property="og:image" content={seo.image} />
{/* Twitter Card */}
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:creator" content={twitter} />
<meta name="twitter:title" content={seo.title} />
<meta name="twitter:description" content={seo.description} />
<meta name="twitter:image" content={seo.image} />
</Helmet>
</>
);
}}
/>
);
...
完成です!
SEO.jsx
はlayout
に置くこともできますし、
posts
みたいなテンプレートに置くことで、デフォルトな情報ではなく、そのページに特化した情報を渡すこともできます。
...
return (
<Layout>
<SEO
title={title}
description={post.frontmatter.description || post.excerpt || ' '}
image={image}
pathname={post.frontmatter.path}
article
/>
...
デプロイする
お疲れさまです!
デプロイにはいろんな方法があります。
githubページとか、Netlify, Surge, Herokuなど。
個人的にはNetlifyとgithubを連携して、ブログを更新するために自動deployする方法が好きです。
詳しい内容は下をご参考ください。
https://www.gatsbyjs.org/docs/hosting-on-netlify/
最後に
いかがだったでしょうか。
長文お読みいただきありがとうございます。
説明が下手な部分や、省略してしまった部分もあり、読みにくい文章になってたらごめんなさい。
この記事で少しでもGatsbyへの理解が深まったなら幸いです。