ブログをWordpress+ロリポップからGatsbyJS+Netlifyに移行した
Reactもご無沙汰だしGatsbyも初めてなのでメモりながらがんばっていくぞ!という記録 WP→Gatsby完全移行をお考えの方の参考になれば。
全体の流れ
- 記事を移行する
- プラグインを利用してmarkdown形式にする
- markdownになりきれてない部分を置換・修正
- GatsbyJSでgatsby-theme-blogを利用したフロントを作成する
- 記事一覧ページに追加
- 記事のタグ・カテゴリの表示
- ページャー
- タグ・カテゴリー別一覧ページを追加
- 記事詳細ページに追加
- 記事のタグ・カテゴリの表示
- コメント機能(Disqusの表示)
- 選択箇所を引用しTwitterでシェアする機能
- Google Analyticsの設定
- 記事一覧ページに追加
- github
- リポジトリ作成してソース一式push
- Netlify
- Githubのリポジトリと連携してビルド設定する
- ドメインの設定
記事の移行
記事のエクスポートはpluginにお世話になりましたが、イマイチ期待通りとはならず以下修正。 幸いにも(?)記事数が少なかったのでほとんど手作業で対応してしまった。
以下はプラグインで対応
記事の移行 WP Gatsby Markdown Exporter
WP Gatsby Markdown Exporter – WordPress プラグイン | WordPress.org 日本語
URL一覧の出力 Export All URLs
Export All URLs – WordPress プラグイン | WordPress.org 日本語
メディアをuploads以下じゃなくて記事と同じディレクトリに置きたい
量が多かったらシェルスクリプト書くかなと思ったけど、そんなに画像が存在しなくて手で移動した。
slideshare, github gist, youtubeなどなど埋め込み系
以下のプラグインを使う事にした。
gatsby-remark-embedder
gatsby-remark-embed-gist
amazonへのリンク
これも数が少なかったので手動で書き換え。 WPのプラグインで出力されていたものを置き換えた。
シンタックスハイライトにプラグインを使っていた名残
<pre class="lang:default decode:true ">
というシンタックスハイライト用のタグが残ってしまったので、テキストエディタの置換などを利用して手作業で変更していく。
md形式のシンタックスハイライトは言語ごとに名前を入れていかないといけないのでちょっとめんどくさい
コメントの引継ぎ
記事が間違っていたりする可能性が非常に高いのでコメント機能は引き続き欲しいよねと思ってDisqusを導入することにした(後述) 既存コメントについては件数も少なかったので引継ぎはナシということにしました。 (コメントくださった方々、ありがとうございました!)
実装していく
シンタックスハイライトしたいので
npm install gatsby-transformer-remark gatsby-remark-prismjs prismjs
いろいろ表示させたいので
npm install gatsby-remark-embedder
github gistを表示させたいので
npm install gatsby-remark-embed-gist
mdxを使いたいので
npm install @mdx-js/mdx @mdx-js/react gatsby-plugin-mdx
embed-gistのスタイルシートがきかない問題
mdxにしたらいい感じにならないので外部CSSを手動で読み込むようにする
参考:Embed gist on gatsby-mdx page · Issue #12 · weirdpattern/gatsby-remark-embed-gist · GitHub
追加開発
- 一覧・詳細に記事のタグ・カテゴリを表示
- タグ・カテゴリ別記事一覧ページ
- 一覧ページにページャー
- Disqusの表示
一覧・詳細に記事のタグ・カテゴリを表示
postmetaコンポーネントを作って、一覧・詳細ともにこちらを呼び出すことにした。
import * as React from "react"
import PropTypes from "prop-types"
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faFolder, faCalendar } from "@fortawesome/free-regular-svg-icons"
import { faHashtag } from "@fortawesome/free-solid-svg-icons"
import {Link} from "gatsby";
const _ = require('lodash')
const PostMeta = ({ date, categories, tags }) => {
return (
<div className="post-meta">
<div className="post-meta_item">
<FontAwesomeIcon icon={faCalendar} />
<time className="time" dateTime="{date}">{date}</time>
</div>
{categories.length > 0 && (
<div className="post-meta_item meta-item-list">
<ul className="categories">
{categories.map((cat, i) => {
const cat_uri = '/category/' + fixedEncodeURI(cat) + '/'
return (
<li key={i}><FontAwesomeIcon icon={faFolder} /><Link to={cat_uri} itemProp="url">{cat}</Link></li>
)
})}
</ul>
</div>
)}
{tags.length > 0 && (
<div className="post-meta_item meta-item-list">
<ul className="tags">
{tags.map((tag, i) => {
const tag_uri = '/tag/' + fixedEncodeURI(tag) + '/'
return (<li key={i}><FontAwesomeIcon icon={faHashtag}/><Link to={tag_uri} itemProp="url">{tag}</Link></li>)
})}
</ul>
</div>
)}
</div>
)
}
function fixedEncodeURI(str) {
return encodeURI(_.kebabCase(str).replace(/[.]/g, function(c) {
return '%' + c.charCodeAt(0).toString(16);
}));
}
PostMeta.defaultProps = {
date: '',
categories: [],
tags: [],
}
PostMeta.propTypes = {
date: PropTypes.string,
categories: PropTypes.arrayOf(PropTypes.string),
tags: PropTypes.arrayOf(PropTypes.string),
}
export default PostMeta
これをcomponentとして以下のようにデータ渡して呼び出し
<PostMeta date={post.frontmatter.date} categories={post.frontmatter.category} tags={post.frontmatter.tag} />
postやらfrontmatterやらでまるごと渡してもいいかな・・・と思ったけどこの方がわかりやすいかなって。
元々のブログでも使っていたので、fotawesomeを導入しています。
npm i -S @fortawesome/fontawesome-svg-core @fortawesome/react-fontawesome @fortawesome/free-regular-svg-icons @fortawesome/free-solid-svg-icons @fortawesome/free-brands-svg-icons
カテゴリ・タグの名前が日本語でエクスポートされる問題
タグやカテゴリはWP時代はslugを持っていてそちらをURLに使っていたのですが、今回は全部文字列そのまんまでエクスポートされているので
encodeURI
をかけてURLにすることにしたのですが、タグ名に「vue.js」とかURLとしてそのまま使うのが厄介な文字列がありまして、
解消するために「.」も変換するような関数を通してそこでURLがいい感じになるようにしています。
参考:encodeURIComponent を使ってエスケープされない文字もエンコードしたいとき
URL的に再現しきれない部分はetlifyのリダイレクトかけるかな〜という感じ。
タグ・カテゴリ別記事一覧ページ
templates以下にカテゴリ用テンプレート追加
import React from 'react'
import {graphql} from 'gatsby'
import Layout from '../components/layout'
import Seo from "../components/seo"
import Articles from "../components/articles" // 記事表示部分は一覧・カテゴリ/タグ別一覧で共通なのでコンポーネントに切り出し済み
const Category = ({ pageContext, data, location }) => {
const siteTitle = data.site.siteMetadata?.title || `Title`
const {category} = pageContext
const posts = data.allMdx.nodes
const title = "カテゴリー「" + category + "」の記事一覧"
return (
<Layout location={location} title={siteTitle}>
<Seo title={title} />
<h1 className="archive-title">{title}</h1>
<Articles posts={posts} />
</Layout>
)
}
export default Category
export const pageQuery = graphql`
query($category: String) {
site {
siteMetadata {
title
}
}
allMdx(
sort: { fields: [frontmatter___date], order: DESC },
filter: { frontmatter: {
category: { eq: $category },
status: {eq: "publish"}
} }
) {
nodes {
excerpt
slug
frontmatter {
date(formatString: "YYYY/MM/DD")
title
category
tag
}
}
}
}
`
このテンプレートを使ってgatsby-node
の中で一覧ページが作成されるように処理を追加します。
(略)
exports.createPages = async ({ graphql, actions, reporter }) => {
(略)
const CategoryTmp = path.resolve(`./src/templates/category.js`)
(略)
// 全記事データをカテゴリ・タグ含んで取得
const result = await graphql(
`
query{
allMdx(
sort: { fields: [frontmatter___date], order: ASC },
filter: {frontmatter: {status: {eq: "publish"}}}
) {
nodes {
id
slug
frontmatter {
category
tag
}
}
}
}
`
)
(略)
const posts = result.data.allMdx.nodes
(略)
let categories = []
// ポストのカテゴリを一旦全取得
_.each(posts, node => {
if (_.get(node, 'frontmatter.category')) {
categories.push(node.frontmatter.category[0])
}
})
// lodashのuniqでユニーク化
categories = _.uniq(categories)
categories.map(category => {
const uri = fixedEncodeURI(category)
createPage({
path: `/category/${_.kebabCase(uri)}/`,
component: CategoryTmp,
context: {
category,
},
})
})
}
(略)
function fixedEncodeURI(str) {
return _.kebabCase(str).replace(/[.]/g, function(c) {
return '%' + c.charCodeAt(0).toString(16);
});
}
タグページも同様に生成。
一覧ページにページャー
gatsby-nodeではimportができなかったので以下の対応を行った gatsby-node.js doesn’t allow ES6 import · Issue #7810 · gatsbyjs/gatsby · GitHub
ページの説明通りに取得データにpaginate
をかけるとpageContext
にこんな感じのデータが入るので、これを利用してページャーを作成する
humanPageNumber: 4
limit: 10
nextPagePath: "/page/5"
numberOfPages: 6
pageNumber: 3
previousPagePath: "/page/3"
skip: 30
とってもシンプルなコンポーネントとして用意
import * as React from "react"
import PropTypes from "prop-types"
import { Link } from "gatsby";
const Pagination = ({ data }) => {
return (
<div className="pagination">
{data.numberOfPages > 1 && (
<p className="page-data">{data.humanPageNumber} / {data.numberOfPages}</p>
)}
<ul>
{data.previousPagePath && (
<li><Link to={data.previousPagePath}>前のページ</Link></li>
)}
{data.nextPagePath && (
<li><Link to={data.nextPagePath}>次のページ</Link></li>
)}
</ul>
</div>
)
}
Pagination.defaultProps = {
data: {}
}
Pagination.propTypes = {
data: PropTypes.object,
}
export default Pagination
Disqusの表示
Disqusに登録してshortnameをゲットしたらあとはプラグインを利用します。
gatsby-plugin-disqus
gatsby-config
のpluginsに設定を追加する
disqus_shortnameは環境変数に入れたのを読み込んでます。
{
resolve: `gatsby-plugin-disqus`,
options: {
shortname: process.env.DISQUS_SHORTNAME
}
}
呼び出したい箇所で以下のように呼び出す。
const disqusConfig = {
url: post_url,
identifier: post.id,
title: post.frontmatter.title,
}
<Disqus config={disqusConfig} />
ちなみに、exampleで紹介されている以下のような書き方だとidentifierがundefinedだよみたいなエラーが出るので上記のconfigで読み込む形にしました。
const PostTemplate = () => (
<>
/* Page Contents */
<Disqus
identifier={post.id}
title={post.title}
url={`${config.siteUrl}${location.pathname}`}
/>
</>
)
Github
Githubで何か特別に設定する必要はなく、リポジトリ作成→pushでOK プライベートリポジトリでもNetlifyで利用可能。
Netlify
Githubとの連携などは案内にそってやっていくと問題なく完了する。
GatsbyJS製のサイトね!と、Netlifyがいい感じのデプロイ設定をしてくれたのでデプロイ周りはこれといって追加でやったことはありません。
ドメインの接続
ドメイン(skylarking.me)は現在のロリポップサーバーに向いたまま、サブドメイン(blog.skylarkineg.me)はNetlifyを向いてほしい。
Netlifyでやること
[site setting]→[domain management]からadd custom domainで追加したいサブドメインを入力する。
ドメイン管理からやること
ドメインを管理しているムームードメインのコンパネからDNSをカスタム設定してサブドメインにCNAMEをあてがう。
これでドメイン設定完了。楽〜!これが無料で使えるなんて! ドメイン代のみで独自ドメインのサイトが持てると聞いてはいたが、スモールスタートにめちゃくちゃいいねと改めて感じました。
今後考える
コンテンツのこと
もし記事や画像がすごく増えたらソースに持つのはどうかなと思うのでやり方考えないといけないなぁ。 Contentful, netlifycms, MicroCMSなどを使ってコンテンツを分離するというのが一番無難なんだろうな。 ContentfulとMicroCMSは少し触ってみたけど、コンテンツとして持てる本文の自由度の低さが少し気になってしまった。
Netlify DNS
今回はサブドメインのみNetlifyに向ける構成なのでNetlify DNSを使うことは諦めたけど Netlify CDN使えたら嬉しいので構成考えてみようかしら