Lighthouse の点数が伸び悩むので Nuxt から Gatsby に変えてみました。点数も 70 点から 92 点に跳ね上がりました。詳しい手順は公式ページをみるとして Nuxt から Gatsby に移行に関連したものを中心に残しておきたいと思います。
まずは Gatsby 直後の Lighthouse の点数がこちらです。いきなりこの点数、Gatsby の効果は絶大だと感じました。
Gatsby
最初に Gatsby Starter Blog(https://www.gatsbyjs.com/starters/gatsbyjs/gatsby-starter-blog)に従いblogテンプレートからスタートです。
gatsby new my-gatsby-project https://github.com/gatsbyjs/gatsby-starter-blog
全体の構成みてみると Nuxt とそれほど全体の構成は変わらないかなと思いました。 Nuxt との違いはコンテンツを GraphQL から取り出すところとロジックと UI 部分が混ざっており Vue に慣れているとちょっと昔の Jquery 時代に戻ったような感じがしてしまうのは私だけでしょうか。
また画像ファイルの扱いがちょっと癖あるかなと思いました。これも慣れだと思います。全体の構成がわかったのでいよいよ Nuxt からの移植作業に取り掛かります。
コンテンツの移行
content の構成は記事ごとにフォルダを作りその中に index.md として作るようです。今回記事で使用する画像もそのフォルダに一緒にいれることにしました。また Nuxt の記事の日付は md ファイルの更新日を利用していましたが、gatsby ではフロントマターに date を設定しているようでした。調べるの面倒なので、サンプルに合わせて同じように日付をフロントマターに入れることにしました。合わせて hero 画面もフロントマターに今回設定することにしました。また prism の書き方も少し違うようなのでこれも合わせて変換していきます。
これを手作業で移動するのは難儀なので、以下のような簡単なスクリプトを作って移行することにしました。
#!/bin/sh
# nuxt -> gatsby
# 記事ファイル名でフォルダを作成してその中に記事ファイルをindex.mdに変更して格納する。
# また記事の画像も同じフォルダに入れる。
# フロントマターにdateとhero画像名を追加する。
# 日付は記事ファイルの更新日、heroは記事ファイル名.jpg
# prism ヘッダ変更 ```language [タイトル] -> ```language:title=タイトル
# input
org=./org #記事フォルダ
hero=./hero #hero画像フォルダ
img=./images #記事で使用している画像フォルダ
# output
out=./out
for item in $org/*.md
do
file=`echo ${item} | sed -e "s/\.md//g" | sed -e "s/\.\/org\///g"`
fileDate=`date -r ${item} +%Y-%m-%dT%H:%M:%S.000Z`
echo $file $fileDate
mkdir $out/$file
cp -a $item $out/$file/index.md
cp -a $hero/$file.jpg $out/$file
cp -a $img/$file* $out/$file
sed -i '' -e "s/^description/date: ${fileDate}\nhero: ${file}\.jpg\ndescription/g" $out/$file/index.md
sed -i '' -e "s/\(\`\`\`.*\).*\[\(.*\)\]/\1\:title\=\2/g" $out/$file/index.md
sed -i '' -e "s/!](\/images\//!\]\(/g" $out/$file/index.md
done
Gatsby と plugin のインストール
今回、以下の plugin を入れることにしました。最初 gatsby-plugin-google-analytics を入れましたが、Google analystic V4 だとうまく行かなそうなので、手作業で Google が用意したスクリプトを追加しました。また prism のタイトルは gatsby-remark-prismjs-title を入れましたが気に食わなかったので、gatsby-remark-code-titles を利用することにしました。
yarn global add gatsby-cli
gatsby new akiboi-blog https://github.com/gatsbyjs/gatsby-starter-blog
yarn add node-sass gatsby-plugin-sass
yarn add @fortawesome/fontawesome-svg-core
yarn add @fortawesome/react-fontawesome
yarn add @fortawesome/free-solid-svg-icons
~~ yarn add gatsby-plugin-google-analytics ~~
yarn add gatsby-plugin-robots-txt
yarn add gatsby-plugin-sitemap
yan add gatsby-transformer-remark
yarn add gatsby-remark-prismjs prismjs
~~yarn add gatsby-remark-prismjs-title~~
yarn add gatsby-remark-code-titles
あとは plugin に合わせて gatsby-config.js を修正すれば完了です。こちらは割愛します。あとは最初に作ったコンテンツを akiboi-blog/content/blog にコピーすれば OK です。scss ファイルはそのまま akiboi-blog/src/scss にコピーすれば OK です。scss ファイルを読めるように blog テンプレートを入れて作成されている layout.js に import してやれば OK です。
import React from "react"
import PropTypes from "prop-types"
import { useStaticQuery, graphql } from "gatsby"
import "../scss/main.scss"
・・・
components の移行
テンプレートのソースを元に移植する方が早いと思います。vue で記載したものを return のところにスクリプトも一緒に入れるみたいな感じです。さほど難しいことはないと思います。動的ページは、gatsby-node.js 、templates そして componetns で実現します。ほとんど雛形がでできているのでこれに手を加える程度です。記事表示するサンプルを載せておきます。私の場合、gatsby-node.js はそのままサンプルのものを使い blog-posts.js は hero 属性を追加した程度でした。
import React from "react"
import { graphql } from "gatsby"
import Article from "../components/Article"
const BlogPostTemplate = ({ data, location }) => {
const article = data.markdownRemark
const { previous, next } = data
return (
<>
<Article article={article} previous={previous} next={next} location={location} />
</>
)
}
export default BlogPostTemplate
export const pageQuery = graphql`
query BlogPostBySlug(
$id: String!
$previousPostId: String
$nextPostId: String
) {
site {
siteMetadata {
title
}
}
markdownRemark(id: { eq: $id }) {
id
excerpt(pruneLength: 160)
html
frontmatter {
title
date(formatString: "MMMM DD, YYYY")
description
hero {
childImageSharp {
fluid(maxWidth: 1024,quality:100) {
...GatsbyImageSharpFluid
}
}
}
}
}
previous: markdownRemark(id: { eq: $previousPostId }) {
fields {
slug
}
frontmatter {
title
date(formatString: "MMMM DD, YYYY")
description
hero {
childImageSharp {
fluid(maxWidth: 1024,quality:100) {
...GatsbyImageSharpFluid
}
}
}
}
}
next: markdownRemark(id: { eq: $nextPostId }) {
fields {
slug
}
frontmatter {
title
date(formatString: "MMMM DD, YYYY")
description
hero {
childImageSharp {
fluid(maxWidth: 1024,quality:100) {
...GatsbyImageSharpFluid
}
}
}
}
}
}
`
フロントマターに hero で指定した画像を表示するようにしています。
import React from "react"
import Image from "gatsby-image"
import Layout from "../components/layout"
import SEO from "../components/seo"
import FooterNavi from "../components/FooterNavi"
const Article = ({ location, article, previous, next }) => {
const title = article.frontmatter.title
const description = article.frontmatter.description
const html = article.html
const hero = article.frontmatter.hero
const f = true
return (
<>
<Layout location={location} title={title}>
<SEO title={title} description={description} article={f} hero={hero} />
<div className="contents">
<div>
<Image fluid={article.frontmatter.hero.childImageSharp.fluid} />
</div>
<article
className="blog-post"
itemScope
itemType="http://schema.org/Article"
>
<h1 className="title">{title}</h1>
<section
dangerouslySetInnerHTML={{ __html: html }}
itemProp="articleBody"
/>
</article>
<hr />
<nav className="blog-post-nav">
<FooterNavi previous={previous} next={next} />
</nav>
</div>
</Layout>
</>
)
}
export default Article
HamburgerMenu
HamburgerMenu も結局大した修正なしに移行することができました。最初は他サンプルとかみてましたが、なんでそんな複雑な処理するのだろうと悩んでしまいました。React ではステートフックがあるのでこれを利用して on、off がに合わせてメニューを開いたりできました。また class を追加する場合どうするのだろうと思ってましたが単純でした。on、off に合わせて is-active クラスを追加することができました。
import React, { useState } from 'react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faHome, faServer, faNetworkWired, faGlobe, faIcons, faMortarPestle } from '@fortawesome/free-solid-svg-icons'
import { Link } from "gatsby"
import Logo from "./Logo"
export const Header = () => {
const [open, setOpen] = useState(false)
const menus = [
{
link: '/news/',
icon: faHome,
text: '新着',
},
・・・
];
return (
<div className="menu">
<nav>
・・・
<div className="sp-menu">
<div className="hamburger">
<div className={"hamburger-line" + " " + (open ? "is-active" : "")} onClick={() => setOpen(!open)}>
<span></span>
<span></span>
<span></span>
</div>
</div>
{open && (
<div className="hamburger-menu">
<div className="logo"><Logo /></div>
<ul className="menu-list">
{menus.map(menu => (
<li><Link to={menu.link}><FontAwesomeIcon icon={menu.icon} /> {menu.text}</Link></li>
))}
</ul >
</div >
)}
</div>
</nav>
</div>
)
}
export default Header
scss ファイルは変更なしでいけました。
・・・
@include screen-mq(sm) {
header {
.menu {
.pc-menu {
display: none;
}
.sp-menu {
display: flex;
width: 100%;
position: fixed;
top: 0;
left: 0;
justify-content: center;
.hamburger-menu {
width: 100%;
height: 100vh;
background: $sp_background_menu_color;
padding: 2rem 0;
.logo {
flex-direction: column;
justify-content: center;
padding: 1rem 0;
width: auto;
text-align: center;
.wrapper {
display: flex;
justify-content: center;
align-items: center;
}
}
.menu-list {
display: flex;
flex-direction: column;
align-items: center;
li {
list-style: none;
padding: 1rem 0;
}
}
}
.hamburger {
width: 60px;
height: 60px;
position: fixed;
top: 0;
right: 0;
.hamburger-line {
width: 36px;
height: 30px;
margin-top: 15px;
margin-left: auto;
margin-right: auto;
position: relative;
cursor: pointer;
span {
width: 100%;
height: 2px;
background: $main_font_color;
display: block;
transition: 0.6s;
position: absolute;
&:first-child {
top: 0;
}
&:nth-child(2) {
top: 14px;
}
&:last-child {
bottom: 0;
}
}
&.is-active {
span {
transition: 0.6s;
&:first-child {
transform: rotate(45deg);
top: 50%;
}
&:nth-child(2) {
opacity: 0;
}
&:last-child {
transform: rotate(-45deg);
top: 50%;
}
}
}
}
}
}
}
}
}
あと微調整で prism 関連で少し手直して完了です。以上で移行は完了です。コードはもう少し綺麗にしたい気持ちはありますが、一通りこれで完了です。
コード 比較
最後にコード数を比較したいと思います。イメージは gatsby が少ないような気がするのですけど。
Nuxt
No | Language | files | blank | comment | code |
---|---|---|---|---|---|
1 | Sass | 7 | 6 | 2 | 709 |
2 | Vuejs Component | 12 | 0 | 22 | 266 |
3 | TypeScript | 2 | 0 | 0 | 115 |
4 | SUM | 21 | 6 | 24 | 1090 |
Gatsby
No | Language | files | blank | comment | code |
---|---|---|---|---|---|
1 | Sass | 7 | 6 | 2 | 740 |
2 | JavaScript | 10 | 26 | 14 | 498 |
3 | TypeScript | 5 | 8 | 1 | 197 |
4 | SUM | 22 | 40 | 17 | 1435 |
結果は Gastsby が 345step も多くなりました。Saas は少し手直ししたので 31step はいいとして JavaScript が多いですね。ロジック自体は少ないと思いますが、GraphQL の部分が多いためかなと思います。今回は、テンプレートベースに移行しましたので、保守しやすいようにもう少しソースコードは見直したいと思います。
今回は、Nuxt を Gatsby に移行してみました。満足いくスコアを出せましたが、アプリ屋さんとしては全部 Typescript にしたいし、もう少し React と付き合いたいと思います。