静的サイトジェネレーターの比較とHugoに決めた理由(2019年版)

技術的関心を持ち続けるために WordPress の運用をし続けてきましたが、記事の更新頻度に対してブログシステムのメンテナスコストのバランスが良くないと思ったので、静的サイトジェネレーターで再構成することにしました。なにより、wp-login.phpwp-admin/ に対するログインの試行をログで見かけるたびに気持ちの良いものではないと思ったのが主な動機です。ただ、WordPress はアップグレードの仕組みが優秀で、ボタンクリックするだけで作業そのものは完了しますので(これまで失敗したことがなかった)、その点、良くできていると思います。

以下、つらつらと特徴をメモしていきますが、結論 Hugo を選択しています。

2021/11/14更新)静的サイトジェネレータの選定に関する最新記事を執筆しました。JavascriptベースのSSGフレームワーク(Next.js、11ty、Gatsby.js)に関するまとめと、結果的に改めてHugoを選定した理由を記載しています。

fand.jp 静的サイトジェネレーターを比較して再びHugoにした理由(2021年版) | Web勉強ノートブック Hatena Bookmark Counter for https://fand.jp/static-site-generator-trends-2021/

2022/12/11更新)静的サイトジェネレーターの入門記事を執筆しました。そもそも静的サイトとは?といった基礎を勉強したい方は参考にしてください。

fand.jp 静的サイトジェネレーターとは何か?WordPress などの動的サイトとの比較と基礎のまとめ | Web勉強ノートブック Hatena Bookmark Counter for https://fand.jp/what-is-static-site-generator/

静的サイトジェネレーターの状況

まず、静的サイトジェネレーターにてググった際に上位ページいくつかから出てくるキーワードを抽出(+記号の数は見かけた頻度のメモ。

  • HUGO ++++
  • Hexo ++++
  • Jekyll +++
  • GatsbyJS +++
  • VuePress
  • React Static
  • Phenomic
  • Metalsmith
  • Middleman
  • Octopress

続いて、Sitatic Generator をまとめているサイトがあるようで、GitHub stars による sorted。数を並べすぎても混乱するので上位7つをピックアップ。

https://www.staticgen.com/

Generator Stars Languages Templates
Jekyll 37,489 Ruby Liquid
Next 36,819 JavaScript React
Hugo 34,574 Go Go
Gatsby 34,101 JavaScript React
Hexo 26,193 JavaScript EJS, Pug, Haml, Swig, Nunjucks, Mustache, Handlebars, Twig, Marko
GitBook 20,604 JavaScript Jinja2
Nuxt 19,648 JavaScript Vue

現在、単純に 静的サイトジェネレーター としてググると、WordPressよりも優れている理由 系の記事が割と目立つ。なのでBlog的な使い方としての静的サイトジェネレーターという並びが多いような印象がしていて、設計書、ガイドラインやチュートリアル的なドキュメントを生成するためのジェネレーター(GitBookなど)はあまり出てこないようだ。今回はブログ置き換え対象としてのジェネレーター探しなのでよいのだけど。

ここまでの傾向から、以下の並びで使い勝手を調べてみる。

  • Jekyll
  • Hugo
  • Gatsby
  • Hexo

StaticGen だと Nuxt も上位にいるものの、これはフレームワークであって静的サイトジェネレーターとは違う位置づけという認識なので外してる。

静的サイトジェネレーターに求める機能要件

続いて簡単な動作確認をしていこうと思うのだけど、ブログで欲しい要件を以下の通りとする。

  • 標準機能で Markdown にて記事を執筆、メンテできること
  • ブログデザイン・テンプレートの選択バリエーションがいくつかあること(最初は手っ取り早く移行し終えたいため)
  • テンプレートのカスタマイズ(≒若干のタグ追加とかのレベル)がわかりやすいこと。構成理解が複雑でないこと

検証構成としては、デフォルトでのジェネレート、および1記事作成してのジェネレートを “基本動作確認” としつつ、あとは気分で “カスタマイズ” として追加検証をします。カスタマイズについては癖を把握するためのものなので、全ツール同一条件での比較をするものではありません。

検証用markdownコンテンツ

今回は、

わたし、定時で帰ります。 - Wikipedia

からテキストを引用して以下のMarkdownでサイト生成する。

わたし、定時で帰ります。
===================

『わたし、定時で帰ります。』(わたし、ていじでかえります。)は、朱野帰子による日本の小説[1]。『yom yom』(新潮社)にて41号(2016年8月)から45号(2017年8月)まで連載された。2019年4月16日より、TBSテレビ「火曜ドラマ」にて吉高由里子主演でテレビドラマ化される[1]。

## 登場人物
### 主人公とその同僚
- 東山 結衣
  - 制作部ディレクタ。32歳。
- 種田 晃太郎
  - サブマネジャー。結衣の元婚約者。

## テレビドラマ
2019年4月16日よりTBSテレビ「火曜ドラマ」(毎週火曜22時開始)にて放送している[2]。主演は吉高由里子[1]。

なお、『火曜ドラマ』枠について、本作第2話までは終了時刻を23時7分とするが、2019年5月改編で『NEWS23』(月 - 木曜日版)の10分前拡大(23時 - 23時56分に変更)により、正時スタートに復すことから、本作第3話以降の終了時刻は22時57分に繰り上げとなる予定。

Jekyll

GitHub Pages で使われていることもあってブランドはできているのだと思う。名前もよく聞く。

GitHub Pages の無料ホスティング
ホスティング会社との取引にうんざりしていませんか? GitHub Pages は Jekyll で動いています。 だから、あなたも自分のサイトを簡単に GitHub にデプロイできます。 しかも、 カスタムドメイン名を含む全てが無料です。 GitHub Pages についてもっと知る →

https://jekyllrb-ja.github.io/

Jekyll 1. 基本動作

クイックスタートに記載の下記内容はスムーズに動作した。

~ $ gem install jekyll
~ $ jekyll new myblog
~ $ cd myblog
~/myblog $ jekyll serve
# => <http://localhost:4000> を見てください

ディレクトリ構成。_posts ディレクリに対して記事を保存していく。

├── 404.html
├── Gemfile
├── Gemfile.lock
├── _config.yml
├── _posts
│   ├── 2019-04-26-welcome-to-jekyll.markdown
│   └── 2019-04-30-kaerimasu.markdown
├── _site
│   ├── 404.html
│   ├── about
│   │   └── index.html
│   ├── assets
│   │   ├── main.css
│   │   └── minima-social-icons.svg
│   ├── feed.xml
│   ├── index.html
│   ├── jekyll
│   │   └── update
│   │       └── 2019
│   │           └── 04
│   │               └── 26
│   │                   └── welcome-to-jekyll.html
│   └── tv
│       └── 2019
│           └── 04
│               └── 15
│                   └── kaerimasu.html
├── about.md
└── index.md

ここでは 2019-04-30-kaerimasu.markdown となっていて、いかにも4/30公開記事のように見えがちだがサイト生成動作には関係ないようで、実際には .markdown ファイル内のヘッダから生成されていた。また、未来日を指定しているとファイル生成がされず表示されなかった。(となると日時指定での公開ってどうなるのだろうか?よくよく考えれば静的ファイルのため当たり前の挙動ではあるのだけど、定期ジョブで叩くような仕掛けが必要なのだろうか。あとで調べる)

---
layout: post
title:  "わたし、定時で帰ります。"
date:   2019-04-15 12:00:00 +0900
categories: tv
---
わたし、定時で帰ります。
===================

『わたし、定時で帰ります。』(わたし、ていじでかえります。)は、朱野帰子による日本の小説[1]。『yom yom』(新潮社)にて41号(2016年8月)から45号(2017年8月)まで連載された。2019年4月16日より、TBSテレビ「火曜ドラマ」にて吉高由里子主演でテレビドラマ化される[1]。

## 登場人物
### 主人公とその同僚
- 東山 結衣
  - 制作部ディレクタ。32歳。
- 種田 晃太郎
  - サブマネジャー。結衣の元婚約者。

## テレビドラマ
2019年4月16日よりTBSテレビ「火曜ドラマ」(毎週火曜22時開始)にて放送している[2]。主演は吉高由里子[1]。

なお、『火曜ドラマ』枠について、本作第2話までは終了時刻を23時7分とするが、2019年5月改編で『NEWS23』(月 - 木曜日版)の10分前拡大(23時 - 23時56分に変更)により、正時スタートに復すことから、本作第3話以降の終了時刻は22時57分に繰り上げとなる予定。

Jekyll 2. サイトテーマの充実度

Jekyll Themes

1ページ20テーマ × 13ページ なので 260テーマ弱あった(2019/04/26現在)

Jekyll 3. カスタマイズ

_config.yml に以下の設定をするとGitHub Flavored Markdownも有効になる。

kramdown:
  input: GFM

そのほか、_config.yml を見れば初期設定がどうなっているのかはだいぶ想像がつ。

プラグインまわり:

もし GitHub Pages の機能を利用してサイトを公開しようとしている場合は以下にある制約条件を理解しておく必要があるみたい。 Adding Jekyll plugins to a GitHub Pages site - GitHub Help

Hugo

The world’s fastest framework for building websites
Hugo is one of the most popular open-source static site generators. With its amazing speed and flexibility, Hugo makes building websites fun again.

https://gohugo.io/

Hugo 1. 基本動作

$ hugo version
Hugo Static Site Generator v0.55.4/extended darwin/amd64 BuildDate: unknown

$ hugo new site quickstart
$ cd quickstart
$ git init
$ git submodule add <https://github.com/budparr/gohugo-theme-ananke.git> themes/ananke
$ echo 'theme = "ananke"' >> config.toml
$ hugo new posts/my-first-post.md
$ hugo server -D

ファイルツリー構造:

.
├── archetypes
│   └── default.md
├── config.toml
├── content
│   └── posts
│       └── my-first-post.md
├── data
├── layouts
├── resources
│   └── _gen
│       ├── assets
│       └── images
├── static
├── themes
│   └── ananke
│       ├── CHANGELOG.md
│       ├── LICENSE.md
│       ├── README.md
│       ├── archetypes
│       │   └── default.md
│       ├── data
│       │   └── webpack_assets.json
│       ├── exampleSite
│       │   ├── config.toml
│       │   ├── content
│       │   │   ├── _index.md
│       │   │   ├── about
│       │   │   │   └── _index.md
│       │   │   ├── contact.md
│       │   │   └── post
│       │   │       ├── _index.md
│       │   │       ├── chapter-1.md
│       │   │       ├── chapter-2.md
│       │   │       ├── chapter-3.md
│       │   │       ├── chapter-4.md
│       │   │       ├── chapter-5.md
│       │   │       └── chapter-6.md
│       │   └── static
│       │       └── images
│       │           ├── Pope-Edouard-de-Beaumont-1844.jpg
│       │           ├── Victor_Hugo-Hunchback.jpg
│       │           ├── esmeralda.jpg
│       │           └── notebook.jpg
│       ├── i18n
│       │   ├── de.toml
│       │   ├── en.toml
│       │   └── fr.toml
│       ├── images
│       │   ├── screenshot.png
│       │   └── tn.png
│       ├── layouts
│       │   ├── 404.html
│       │   ├── _default
│       │   │   ├── baseof.html
│       │   │   ├── list.html
│       │   │   ├── single.html
│       │   │   ├── taxonomy.html
│       │   │   └── terms.html
│       │   ├── index.html
│       │   ├── page
│       │   │   └── single.html
│       │   ├── partials
│       │   │   ├── i18nlist.html
│       │   │   ├── menu-contextual.html
│       │   │   ├── new-window-icon.html
│       │   │   ├── page-header.html
│       │   │   ├── site-favicon.html
│       │   │   ├── site-footer.html
│       │   │   ├── site-header.html
│       │   │   ├── site-navigation.html
│       │   │   ├── site-scripts.html
│       │   │   ├── social-follow.html
│       │   │   ├── summary-with-image.html
│       │   │   ├── summary.html
│       │   │   ├── svg
│       │   │   │   ├── facebook.svg
│       │   │   │   ├── github.svg
│       │   │   │   ├── gitlab.svg
│       │   │   │   ├── instagram.svg
│       │   │   │   ├── linkedin.svg
│       │   │   │   ├── mastodon.svg
│       │   │   │   ├── medium.svg
│       │   │   │   ├── new-window.svg
│       │   │   │   ├── twitter.svg
│       │   │   │   └── youtube.svg
│       │   │   └── tags.html
│       │   ├── post
│       │   │   ├── list.html
│       │   │   ├── summary-with-image.html
│       │   │   └── summary.html
│       │   ├── robots.txt
│       │   └── shortcodes
│       │       └── form-contact.html
│       ├── package.json
│       ├── src
│       │   ├── css
│       │   │   ├── _code.css
│       │   │   ├── _hugo-internal-templates.css
│       │   │   ├── _social-icons.css
│       │   │   ├── _styles.css
│       │   │   ├── _tachyons.css
│       │   │   ├── main.css
│       │   │   └── postcss.config.js
│       │   ├── js
│       │   │   └── main.js
│       │   ├── package-lock.json
│       │   ├── package.json
│       │   ├── readme.md
│       │   └── webpack.config.js
│       ├── static
│       │   ├── dist
│       │   │   ├── css
│       │   │   │   └── app.955516233bcafa4d2a1c13cea63c7b50.css
│       │   │   └── js
│       │   │       └── app.3fc0f988d21662902933.js
│       │   └── images
│       │       └── gohugo-default-sample-hero-image.jpg
│       └── theme.toml
└── tree.txt

37 directories, 82 files

  • Basic Usage | Hugo
  • hugo として実行するだけでサマリー(Page数とか)を表示してくれるのがシンプルでよかった
  • デフォルト livereload の様子。どちらかというと、やりたい場合は -watch のようにフラグをセットするパターンが多い印象だけどこれは逆みたい。hugo server --watch=false
  • 何より驚いたのが 動作が早すぎる こと。起動コマンドを叩いてワンテンポ待つツールが圧倒的に多いなか、Hugoは叩いた瞬間に起動が終わってる。最初は気づかなくて、「スタートアップ準備中のログが出てから状況変化しないなぁ」と思ってしまったくらい。
    • ベンチマーク Hugo vs Jekyll: Benchmarked | Forestry.io
    • 爆速なのは間違いないけど1万記事も扱うシーンは限られているし、デプロイ作業を自動化する想定ならば気にする必要はないとも言える
    • 一方、記事作成時に都度サーバーを立ち上げてコンテンツの表示確認をする場合は早い方がいいのだろう。記事執筆モチベーションに影響するという話もあるし(LiveReloadが効いているので1回立ち上げれば十分ではあるが)
---
title: わたし、定時で帰ります。
description: >-
  『わたし、定時で帰ります。』(わたし、ていじでかえります。)は、朱野帰子による日本の小説[1]。『yom
  yom』(新潮社)にて41号(2016年8月)から45号(2017年8月)まで連載された。
date: '2019-04-15 12:00:00 +0900'
categories:
  - tv
tags:
  - tv
---

# わたし、定時で帰ります。

『わたし、定時で帰ります。』(わたし、ていじでかえります。)は、朱野帰子による日本の小説[1]。『yom yom』(新潮社)にて41号(2016年8月)から45号(2017年8月)まで連載された。2019年4月16日より、TBSテレビ「火曜ドラマ」にて吉高由里子主演でテレビドラマ化される[1]。

## 登場人物
### 主人公とその同僚

- 東山 結衣
  - 制作部ディレクタ。32歳。
- 種田 晃太郎
  - サブマネジャー。結衣の元婚約者。

## テレビドラマ

2019年4月16日よりTBSテレビ「火曜ドラマ」(毎週火曜22時開始)にて放送している[2]。主演は吉高由里子[1]。

なお、『火曜ドラマ』枠について、本作第2話までは終了時刻を23時7分とするが、2019年5月改編で『NEWS23』(月 - 木曜日版)の10分前拡大(23時 - 23時56分に変更)により、正時スタートに復すことから、本作第3話以降の終了時刻は22時57分に繰り上げとなる予定。

category や tag は ["one","tow"] のように書くか、上記の通り YAML の配列のように記載。

画像の配置

前提として、MYPROJECT/static/images ディレクトリ画像を配置しておくと以下の通りアクセスができる。

![](/images/img01.png)

一方、Hugoには Shortcode と呼ばれる実装(ショーカットカット的な独自タグ)がある。例えば 、以下のタグを書く。

{{< figure src="/images/img01.png" title="Screenshot" class="center" width="320" height="640" >}}

# 注 ここではカッコを全角で書いていますが、正確には半角にする必要があります。

すると、以下のコードとして展開される。

<figure class="center">
    <img src="/images/img01.png" width="320" height="640"/> <figcaption>
            <h4>Screenshot</h4>
        </figcaption>
</figure>

Hugoを使い続ける上ではメリットがあるのだろうけど、Markdownコンテンツそのものの汎用性が無くなること、および一般的なMarkdownエディタでのプレビュー機能が使えなくなる点から微妙に感じてしまう。

Hugo 2. サイトテーマの充実度

Hugo Themes から好きなテーマを探して、 ./themes ディレクトリ内で git clone する。

Jekyll のテーマギャラリーはズラーっと一覧が並んでいるだけだったけれど、こちらのギャラリーはタグ付けがあるので目的に辿り着きやすいのではないかと思う。

Hugo 3. カスタマイズ

Internal Templates という仕組みがあって、一般的だろう機能はコンフィグファイル(config.toml)にちょこっと設定を記載するだけで機能が有効化されるようになっている。

Internal Templates | Hugo

たとえば Google Analytics:

googleAnalytics = "UA-123-45"

Disqus:

disqusShortname = "yourdiscussshortname"

Twitter Cards:

[params]
  description = "Text about my cool site"
  images = ["site-feature-image.jpg"]

Minify (ファイルの圧縮)は標準コマンドでできてしまう。hugo server で起動した時に見えるソースは普通に改行が入ったままなのだが、./public にある html ファイルを見ると圧縮されている。

hugo --minify

テンプレートファイルを編集したい場合。例えば AdSense を設置したい場合など。

Before:

{{ define "header" }}
   {{/* We can override any block in the baseof file be defining it in the template */}}
  {{ partial "page-header.html" . }}
{{ end }}

{{ define "main" }}
  {{ $section := .Site.GetPage "section" .Section }}
  <article class="flex-l flex-wrap justify-between mw8 center ph3">

    <header class="mt4 w-100">
      <p class="f6 b helvetica tracked">
          {{/*
          CurrentSection allows us to use the section title instead of inferring from the folder.
          <https://gohugo.io/variables/page/#section-variables-and-methods>
          */}}
        {{with .CurrentSection.Title }}{{. | upper }}{{end}}
      </p>
      <h1 class="f1 athelas mb1">
        {{- .Title -}}
      </h1>
      {{/* Hugo uses Go's date formatting is set by example. Here are two formats */}}
      <time class="f6 mv4 dib tracked" datetime="{{ .Date.Format "2006-01-02T15:04:05Z07:00" }}">
        {{- .Date.Format "January 2, 2006" -}}
      </time>
      {{/*
          Show "reading time" and "word count" but only if one of the following are true:
          1) A global config `params` value is set `show_reading_time = true`
          2) A section front matter value is set `show_reading_time = true`
          3) A page front matter value is set `show_reading_time = true`
        */}}
      {{ if (or (eq (.Param "show_reading_time") true) (eq $section.Params.show_reading_time true) )}}
        <span class="f6 mv4 dib tracked"> - {{ .ReadingTime}} minutes read</span>
        <span class="f6 mv4 dib tracked"> - {{ .WordCount}} words</span>
      {{ end }}
    </header>

    <section class="nested-copy-line-height lh-copy serif f4 nested-links nested-img mid-gray pr4-l w-two-thirds-l">
      {{- .Content -}}
      {{- partial "tags.html" . -}}
      <div class="mt6">
        {{ template "_internal/disqus.html" . }}
      </div>
    </section>

    <aside class="w-30-l mt6-l">
      {{- partial "menu-contextual.html" . -}}
    </aside>

  </article>
{{ end }}

After:

--- themes/ananke/layouts/_default/single.html.bk
+++ themes/ananke/layouts/_default/single.html
@@ -44,6 +44,9 @@

     <aside class="w-30-l mt6-l">
       {{- partial "menu-contextual.html" . -}}
+      <div id="adsense">
+        例えばここにAdSense
+      </div>
     </aside>

   </article>

今回 MYPROJECT/THEME/layouts/_default/ のファイルを直接編集したものの、もっと良いお作法があると思うので調べる必要あり。Templates | Hugo

おそらく(一例として) Hugo Future Imperfect Slim テーマの説明にある通り、 MYPROJECT/themes がテーマオリジナルであるのに対して、マイプロジェクト用の修正は MYPROJECT/layouts に同じファイル名で配置していくことで上書きできる理解となる。

You will then have access to the theme at themes/hugo-future-imperfect-slim from within your project folder.
From here, you can customize any of the files for your personal use by creating a dupicating the file at the project level instead of the theme level. For example, if you wanted to change the About Me Page Template (which is located at MYPROJECT/themes/layouts/about/list.html), I would create a new file at MYPROJECT/layouts/about/list.html and change it to what I wanted it to be.

layout/partials を活用することで、以下のように部分的な読み込みができるのでうまく活用すべきだろう。

{{ partial "head.html" }}

サイトマップは組み込みが用意されていて、標準で生成される。必要に応じて上書きやパラメタ変更をすることができる。Sitemap Template | Hugo

Hexo

A fast, simple & powerful blog framework

https://hexo.io/

blog framework と謳っているくらいなので、Blog向けの痒い機能があるのだろうと期待。

Various Plugins
Hexo has a powerful plugin system. You can install more plugins for Jade, CoffeeScript plugins.

Plugins も多数あるようです(2019/04/26現在 290 items との記載あり)。

Hexo 1. 基本動作

Get Started は以下の通り。

$ npm install hexo-cli -g
$ hexo init blog
$ cd blog
$ npm install
$ hexo server

ツリー構成。node_modules は多すぎるので除外。

.
├── _config.yml
├── db.json
├── node_modules
│    ....... 省略
├── package-lock.json
├── package.json
├── scaffolds
│   ├── draft.md
│   ├── page.md
│   └── post.md
├── source
│   └── _posts
│       └── hello-world.md
├── themes
│   └── landscape
│       ├── Gruntfile.js
│       ├── LICENSE
│       ├── README.md
│       ├── _config.yml
│       ├── languages
│       │   ├── de.yml
│       │   ├── default.yml
│       │   ├── es.yml
│       │   ├── fr.yml
│       │   ├── ja.yml
│       │   ├── ko.yml
│       │   ├── nl.yml
│       │   ├── no.yml
│       │   ├── pt.yml
│       │   ├── ru.yml
│       │   ├── zh-CN.yml
│       │   └── zh-TW.yml
│       ├── layout
│       │   ├── _partial
│       │   │   ├── after-footer.ejs
│       │   │   ├── archive-post.ejs
│       │   │   ├── archive.ejs
│       │   │   ├── article.ejs
│       │   │   ├── footer.ejs
│       │   │   ├── gauges-analytics.ejs
│       │   │   ├── google-analytics.ejs
│       │   │   ├── head.ejs
│       │   │   ├── header.ejs
│       │   │   ├── mobile-nav.ejs
│       │   │   ├── post
│       │   │   │   ├── category.ejs
│       │   │   │   ├── date.ejs
│       │   │   │   ├── gallery.ejs
│       │   │   │   ├── nav.ejs
│       │   │   │   ├── tag.ejs
│       │   │   │   └── title.ejs
│       │   │   └── sidebar.ejs
│       │   ├── _widget
│       │   │   ├── archive.ejs
│       │   │   ├── category.ejs
│       │   │   ├── recent_posts.ejs
│       │   │   ├── tag.ejs
│       │   │   └── tagcloud.ejs
│       │   ├── archive.ejs
│       │   ├── category.ejs
│       │   ├── index.ejs
│       │   ├── layout.ejs
│       │   ├── page.ejs
│       │   ├── post.ejs
│       │   └── tag.ejs
│       ├── package.json
│       ├── scripts
│       │   └── fancybox.js
│       └── source
│           ├── css
│           │   ├── _extend.styl
│           │   ├── _partial
│           │   │   ├── archive.styl
│           │   │   ├── article.styl
│           │   │   ├── comment.styl
│           │   │   ├── footer.styl
│           │   │   ├── header.styl
│           │   │   ├── highlight.styl
│           │   │   ├── mobile.styl
│           │   │   ├── sidebar-aside.styl
│           │   │   ├── sidebar-bottom.styl
│           │   │   └── sidebar.styl
│           │   ├── _util
│           │   │   ├── grid.styl
│           │   │   └── mixin.styl
│           │   ├── _variables.styl
│           │   ├── fonts
│           │   │   ├── FontAwesome.otf
│           │   │   ├── fontawesome-webfont.eot
│           │   │   ├── fontawesome-webfont.svg
│           │   │   ├── fontawesome-webfont.ttf
│           │   │   └── fontawesome-webfont.woff
│           │   ├── images
│           │   │   └── banner.jpg
│           │   └── style.styl
│           ├── fancybox
│           │   ├── blank.gif
│           │   ├── fancybox_loading.gif
│           │   ├── fancybox_loading@2x.gif
│           │   ├── fancybox_overlay.png
│           │   ├── fancybox_sprite.png
│           │   ├── fancybox_sprite@2x.png
│           │   ├── helpers
│           │   │   ├── fancybox_buttons.png
│           │   │   ├── jquery.fancybox-buttons.css
│           │   │   ├── jquery.fancybox-buttons.js
│           │   │   ├── jquery.fancybox-media.js
│           │   │   ├── jquery.fancybox-thumbs.css
│           │   │   └── jquery.fancybox-thumbs.js
│           │   ├── jquery.fancybox.css
│           │   ├── jquery.fancybox.js
│           │   └── jquery.fancybox.pack.js
│           └── js
│               └── script.js
├── tree.txt
└── yarn.lock

1026 directories, 6385 files

hexo generate とすると、public ディレクトリが出来てファイルが生成される。

$ hexo generate
INFO  Start processing
INFO  Files loaded in 107 ms
INFO  Generated: index.html
INFO  Generated: archives/index.html
INFO  Generated: fancybox/blank.gif
INFO  Generated: fancybox/jquery.fancybox.css
INFO  Generated: fancybox/fancybox_loading@2x.gif
INFO  Generated: fancybox/fancybox_loading.gif
INFO  Generated: fancybox/fancybox_overlay.png
INFO  Generated: fancybox/fancybox_sprite.png
INFO  Generated: fancybox/fancybox_sprite@2x.png
INFO  Generated: archives/2019/04/index.html
INFO  Generated: archives/2019/index.html
INFO  Generated: js/script.js
INFO  Generated: css/fonts/fontawesome-webfont.eot
INFO  Generated: fancybox/helpers/jquery.fancybox-buttons.css
INFO  Generated: fancybox/jquery.fancybox.pack.js
INFO  Generated: fancybox/helpers/jquery.fancybox-buttons.js
INFO  Generated: fancybox/helpers/jquery.fancybox-thumbs.css
INFO  Generated: css/fonts/FontAwesome.otf
INFO  Generated: fancybox/helpers/jquery.fancybox-media.js
INFO  Generated: css/fonts/fontawesome-webfont.woff
INFO  Generated: css/style.css
INFO  Generated: fancybox/helpers/fancybox_buttons.png
INFO  Generated: fancybox/helpers/jquery.fancybox-thumbs.js
INFO  Generated: css/fonts/fontawesome-webfont.svg
INFO  Generated: css/fonts/fontawesome-webfont.ttf
INFO  Generated: 2019/04/26/hello-world/index.html
INFO  Generated: fancybox/jquery.fancybox.js
INFO  Generated: css/images/banner.jpg
INFO  28 files generated in 566 ms


## 記事ファイルを作成して追加 generate
$ hexo generate
INFO  Start processing
INFO  Files loaded in 219 ms
INFO  Generated: 2019/04/26/hello-world/index.html
INFO  Generated: index.html
INFO  Generated: archives/2019/04/index.html
INFO  Generated: archives/2019/index.html
INFO  Generated: archives/index.html
INFO  Generated: 2019/04/15/karimasu/index.html
INFO  Generated: tags/tv/index.html
INFO  7 files generated in 130 ms

記事コンテンツ。description を書かないと本文から自動抽出してくれるものの、だらしなくなるので別に記載したほうが意図したとおりになるのでよいかと。

---
title: わたし、定時で帰ります。
description: >-
  『わたし、定時で帰ります。』(わたし、ていじでかえります。)は、朱野帰子による日本の小説[1]。『yom
  yom』(新潮社)にて41号(2016年8月)から45号(2017年8月)まで連載された。
date: '2019-04-15 12:00:00 +0900'
tags: tv
---

# わたし、定時で帰ります。

『わたし、定時で帰ります。』(わたし、ていじでかえります。)は、朱野帰子による日本の小説[1]。『yom yom』(新潮社)にて41号(2016年8月)から45号(2017年8月)まで連載された。2019年4月16日より、TBSテレビ「火曜ドラマ」にて吉高由里子主演でテレビドラマ化される[1]。

## 登場人物

### 主人公とその同僚
- 東山 結衣
  - 制作部ディレクタ。32歳。
- 種田 晃太郎
  - サブマネジャー。結衣の元婚約者。

## テレビドラマ

2019年4月16日よりTBSテレビ「火曜ドラマ」(毎週火曜22時開始)にて放送している[2]。主演は吉高由里子[1]。

なお、『火曜ドラマ』枠について、本作第2話までは終了時刻を23時7分とするが、2019年5月改編で『NEWS23』(月 - 木曜日版)の10分前拡大(23時 - 23時56分に変更)により、正時スタートに復すことから、本作第3話以降の終了時刻は22時57分に繰り上げとなる予定。

標準で入っている plugins:

"dependencies": {
  "hexo": "^3.8.0",
  "hexo-generator-archive": "^0.1.5",
  "hexo-generator-category": "^0.1.3",
  "hexo-generator-index": "^0.2.1",
  "hexo-generator-tag": "^0.2.0",
  "hexo-renderer-ejs": "^0.3.1",
  "hexo-renderer-stylus": "^0.3.3",
  "hexo-renderer-marked": "^0.3.2",
  "hexo-server": "^0.3.3"
}

Hexo 2. サイトテーマの充実度

テーマは充実している印象(2019/04/26現在 251 items)。 Themes | Hexo

基本的な考え方は、

  • ./themes に移動する
  • 対象テーマを git clone(あるいは git submodule)して配置する
  • _config.yml にある theme: XXXXX の部分を設置したディレクトリ名(テーマ名)に変更する

というわけでシンプルなのだけど、theme側では前提条件がまちまちなので、npm コマンドを打たなければならないシーンが多い。テーマ切り替える毎に前提条件をチェックしないといけないのは管理コスト的に面倒くさいと思った。そんな頻繁に行うわけではないし、若干の誤差だろうといえばそれまでではあるのだけど。

Hexo 3. カスタマイズ

hexo-generator-feed

$ npm install hexo-generator-feed --save

feed:
  type: atom
  path: atom.xml
  limit: 20
  hub:
  content:
  content_limit: 140
  content_limit_delim: ' '
  order_by: -date

デフォルトテンプレートでも画面右上に /atom.xml へのリンクがあるのだけど、Not Found になってしまう。プラグインを入れるとFeedが生成される。

hexo-generator-seo-friendly-sitemap

$ npm install hexo-generator-seo-friendly-sitemap --save

sitemap:
  path: sitemap.xml
  tag: false
  category: false

hexo-all-minifier

$ npm install hexo-all-minifier --save

# For Mac User, maybe you need to install something more
$ brew install libtool automake autoconf nasm

all_minifier: true

hexo-generator-amp

試してないが、GitHub Star 100超 の定番 plugin ならこれっぽい。 tea3/hexo-generator-amp: AMP ⚡ HTML (Accelerated Mobile Pages) generator for Hexo.

その他

  • tea3/hexo-seo-link-visualizer: Analyze link and visualize the site structure for Hexo
    • かっこいい。記事数が少ないとやっても意味がないのでまた今度。
  • シングルページ(よくある About みたいな目的のページ)を作るのは別途プラグインが必要な様子?Jekyllではデフォルトでできてたけど。現在要件では必要ないので保留とします。
  • asset pipeline による digest付与のような仕組みは探してみたけど見つけられなかった。

Gatsby

Fast in every way that matters
Gatsby is a free and open source framework based on React that helps developers build blazing fast websites and apps

https://www.gatsbyjs.org/

Gatsby は React ベースのフレームワークということで SPA (Single Page Application) なので、ここまでのサイトジェネレータとは大きく性質が異なる。

Gatsby 1. 基本動作

Markdown のファイル表示はデフォルトでは対応していない様子。つまり冒頭要件である『標準機能で Markdown にて記事を執筆、メンテできること』を満たせないことからも、目的が異なる位置づけとなる。Markdown ファイル表示にはひと手間かかりそうなのでデフォルト生成物での簡単な検証に留めることとする。

Adding Markdown Pages | GatsbyJS

npm install -g gatsby-cli
gatsby new gatsby-site
cd gatsby-site
gatsby develop

# Gatsby will start a hot-reloading development environment accessible by default at localhost:8000.
# Try editing the JavaScript pages in src/pages. Saved changes will live reload in the browser.

例に漏れず node_modules によりファイル数が多いので tree 表示は省略(3万ファイルくらい出来ている)。

ここまでで develop モードとして起動している。ブラウザでアクセスするとページが表示される。この時、curl コマンドレベルで取得できるHTMLは以下の通り。HTMLタイトルなどが全くないので、検索エンジンへの登録はこのままだと厳しい状況(これはSPAにおける一般的な課題の一つです)。

$ url <http://localhost:8000/page-2/>

<!DOCTYPE html><html><head><meta charSet="utf-8"/><meta http-equiv="x-ua-compatible" content="ie=edge"/><meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/><title data-react-helmet="true"></title><link rel="shortcut icon" href="/icons/icon-48x48.png?v=008654519ce705ac7bc44303a9014606"/><link rel="manifest" href="/manifest.webmanifest"/><meta name="theme-color" content="#663399"/><link rel="apple-touch-icon" sizes="48x48" href="/icons/icon-48x48.png?v=008654519ce705ac7bc44303a9014606"/><link rel="apple-touch-icon" sizes="72x72" href="/icons/icon-72x72.png?v=008654519ce705ac7bc44303a9014606"/><link rel="apple-touch-icon" sizes="96x96" href="/icons/icon-96x96.png?v=008654519ce705ac7bc44303a9014606"/><link rel="apple-touch-icon" sizes="144x144" href="/icons/icon-144x144.png?v=008654519ce705ac7bc44303a9014606"/><link rel="apple-touch-icon" sizes="192x192" href="/icons/icon-192x192.png?v=008654519ce705ac7bc44303a9014606"/><link rel="apple-touch-icon" sizes="256x256" href="/icons/icon-256x256.png?v=008654519ce705ac7bc44303a9014606"/><link rel="apple-touch-icon" sizes="384x384" href="/icons/icon-384x384.png?v=008654519ce705ac7bc44303a9014606"/><link rel="apple-touch-icon" sizes="512x512" href="/icons/icon-512x512.png?v=008654519ce705ac7bc44303a9014

続けて、公開用の最終生成物をビルドするステップ。

gatsby build
gatsby serve

# info gatsby serve running at: <http://localhost:9000/>

こちらではポート番号が 9000 に変わっている。この状態で同様に html ソースを確認すると、develop 時と比べて違いが見られた(全く別物)。

$ curl <http://localhost:9000/page-2/>

<!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta http-equiv="x-ua-compatible" content="ie=edge"/><meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/><style data-href="/styles.fc4fa5e094d218207796.css">html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}article,aside,details,figcaption,figure,footer,header,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block}audio:not([controls]){display:none;height:0}progress{vertical-align:baseline}[hidden],template{display:none}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:inherit;font-weight:bolder}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}svg:not(:root){overflow:hidden}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}figure{margin:1em 40px}hr{box-sizing:content-box;height:0;overflow:visible}button,input,optgroup,select,textarea{font:inherit;margin:0}optgroup{font-weight:700}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-input-placeholder{color:inherit;opacity:.54}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}html{font:112.5%/1.45em georgia,serif;box-sizing:border-box;overflow-y:scroll}*,:after,:before{box-sizing:inherit}body{color:rgba(0,0,0,.8);font-family:georgia,serif;font-weight:400;word-wrap:break-word;-webkit-font-kerning:normal;font-kerning:normal;-ms-font-feature-settings:"kern","liga","clig","calt";-webkit-font-feature-settings:"kern","liga","clig","calt";font-feature-settings:"kern","liga","clig","calt"}img{max-width:100%;padding:0;margin:0 0 1.45rem}h1{font-size:2.25rem}h1,h2{padding:0;margin:0 0 1.45rem;color:inherit;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;font-weight:700;text-rendering:optimizeLegibility;line-height:1.1}h2{font-size:1.62671rem}h3{font-size:1.38316rem}h3,h4{padding:0;margin:0 0 1.45rem;color:inherit;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;font-weight:700;text-rendering:optimizeLegibility;line-height:1.1}h4{font-size:1rem}h5{font-size:.85028rem}h5,h6{padding:0;margin:0 0 1.45rem;color:inherit;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;font-weight:700;text-rendering:optimizeLegibility;line-height:1.1}h6{font-size:.78405rem}hgroup{padding:0;margin:0 0 1.45rem}ol,ul{padding:0;margin:0 0 1.45rem 1.45rem;list-style-position:outside;list-style-image:none}dd,dl,figure,p{padding:0;margin:0 0 1.45rem}pre{margin:0 0 1.45rem;font-size:.85rem;line-height:1.42;background:rgba(0,0,0,.04);border-radius:3px;overflow:auto;word-wrap:normal;padding:1.45rem}table{font-size:1rem;line-height:1.45rem;border-collapse:collapse;width:100%}fieldset,table{padding:0;margin:0 0 1.45rem}blockquote{padding:0;margin:0 1.45rem 1.45rem}form,iframe,noscript{padding:0;margin:0 0 1.45rem}hr{padding:0;margin:0 0 calc(1.45rem - 1px);background:rgba(0,0,0,.2);border:none;height:1px}address{padding:0;margin:0 0 1.45rem}b,dt,strong,th{font-weight:700}li{margin-bottom:.725rem}ol li,ul li{padding-left:0}li>ol,li>ul{margin-left:1.45rem;margin-bottom:.725rem;margin-top:.725rem}blockquote :last-child,li :last-child,p :last-child{margin-bottom:0}li>p{margin-bottom:.725rem}code,kbd,samp{font-size:.85rem;line-height:1.45rem}abbr,abbr[title],acronym{border-bottom:1px dotted rgba(0,0,0,.5);cursor:help}abbr[title]{text-decoration:none}td,th,thead{text-align:left}td,th{border-bottom:1px solid rgba(0,0,0,.12);font-feature-settings:"tnum";-moz-font-feature-settings:"tnum";-ms-font-feature-settings:"tnum";-webkit-font-feature-settings:"tnum";padding:.725rem .96667rem calc(.725rem - 1px)}td:first-child,th:first-child{padding-left:0}td:last-child,th:last-child{padding-right:0}code,tt{background-color:rgba(0,0,0,.04);border-radius:3px;font-family:SFMono-Regular,Consolas,Roboto Mono,Droid Sans Mono,Liberation Mono,Menlo,Courier,monospace;padding:.2em 0}pre code{background:none;line-height:1.42}code:after,code:before,tt:after,tt:before{letter-spacing:-.2em;content:" "}pre code:after,pre code:before,pre tt:after,pre tt:before{content:""}@media only screen and (max-width:480px){html{font-size:100%}}</style><meta name="generator" content="Gatsby 2.3.32"/><title data-react-helmet="true">Page two | Gatsby Default Starter</title><meta data-react-helmet="true" name="description" content="Kick off your next, great Gatsby project with this default starter. This barebones starter ships with the main Gatsby configuration files you might need."/><meta data-react-helmet="true" property="og:title" content="Page two"/><meta data-react-helmet="true" property="og:description" content="Kick off your next, great Gatsby project with this default starter. This barebones starter ships with the main Gatsby configuration files you might need."/><meta data-react-helmet="true" property="og:type" content="website"/><meta data-react-helmet="true" name="twitter:card" content="summary"/><meta data-react-helmet="true" name="twitter:creator" content="@gatsbyjs"/><meta data-react-helmet="true" name="twitter:title" content="Page two"/><meta data-react-helmet="true" name="twitter:description" content="Kick off your next, great Gatsby project with this default starter. This barebones starter ships with the main Gatsby configuration files you might need."/><link rel="shortcut icon" href="/icons/icon-48x48.png?v=008654519ce705ac7bc44303a9014606"/><link rel="manifest" href="/manifest.webmanifest"/><meta name="theme-color" content="#663399"/><link rel="apple-touch-icon" sizes="48x48" href="/icons/icon-48x48.png?v=008654519ce705ac7bc44303a9014606"/><link rel="apple-touch-icon" sizes="72x72" href="/icons/icon-72x72.png?v=008654519ce705ac7bc44303a9014606"/><link rel="apple-touch-icon" sizes="96x96" href="/icons/icon-96x96.png?v=008654519ce705ac7bc44303a9014606"/><link rel="apple-touch-icon" sizes="144x144" href="/icons/icon-144x144.png?v=008654519ce705ac7bc44303a9014606"/><link rel="apple-touch-icon" sizes="192x192" href="/icons/icon-192x192.png?v=008654519ce705ac7bc44303a9014606"/><link rel="apple-touch-icon" sizes="256x256" href="/icons/icon-256x256.png?v=008654519ce705ac7bc44303a9014606"/><link rel="apple-touch-icon" sizes="384x384" href="/icons/icon-384x384.png?v=008654519ce705ac7bc44303a9014606"/><link rel="apple-touch-icon" sizes="512x512" href="/icons/icon-512x512.png?v=008654519ce705ac7bc44303a9014606"/><link as="script" rel="preload" href="/webpack-runtime-2dde02a234c0aad7ccf9.js"/><link as="script" rel="preload" href="/app-0d901be2d50c18297369.js"/><link as="script" rel="preload" href="/styles-0375cdcc38b87565858c.js"/><link as="script" rel="preload" href="/1-f2577b7d9227c6ab20bd.js"/><link as="script" rel="preload" href="/component---src-pages-page-2-js-e54da239d5d16d705ff3.js"/><link as="fetch" rel="preload" href="/static/d/726/path---page-2-fbc-5a8-0SUcWyAf8ecbYDsMhQkEfPzV8.json" crossorigin="use-credentials"/></head><body><noscript id="gatsby-noscript">This app works best with JavaScript enabled.</noscript><div id="___gatsby"><div style="outline:none" tabindex="-1" role="group"><header style="background:rebeccapurple;margin-bottom:1.45rem"><div style="margin:0 auto;max-width:960px;padding:1.45rem 1.0875rem"><h1 style="margin:0"><a style="color:white;text-decoration:none" href="/">Gatsby Default Starter</a></h1></div></header><div style="margin:0 auto;max-width:960px;padding:0px 1.0875rem 1.45rem;padding-top:0"><main><h1>Hi from the second page</h1><p>Welcome to page 2</p><a href="/">Go back to the homepage</a></main><footer>© <!-- -->2019<!-- -->, Built with<!-- --> <a href="<https://www.gatsbyjs.org>">Gatsby</a></footer></div></div></div><script id="gatsby-script-loader">/*<![CDATA[*/window.page={"componentChunkName":"component---src-pages-page-2-js","jsonName":"page-2-fbc","path":"/page-2/"};window.dataPath="726/path---page-2-fbc-5a8-0SUcWyAf8ecbYDsMhQkEfPzV8";/*]]>*/</script><script id="gatsby-chunk-mapping">/*<![CDATA[*/window.___chunkMapping={"app":["/app-0d901be2d50c18297369.js"],"component---src-pages-404-js":["/component---src-pages-404-js-a1288cb3773ca405fde2.js"],"component---src-pages-index-js":["/component---src-pages-index-js-e8da6d35e52eec291ec9.js"],"component---src-pages-page-2-js":["/component---src-pages-page-2-js-e54da239d5d16d705ff3.js"],"pages-manifest":["/pages-manifest-65bfa2df20d3a86c24f3.js"]};/*]]>*/</script><script src="/component---src-pages-page-2-js-e54da239d5d16d705ff3.js" async=""></script><script src="/1-f2577b7d9227c6ab20bd.js" async=""></script><script src="/styles-0375cdcc38b87565858c.js" asyn

このあたりの挙動は SEO with Gatsby として説明がある。curl では確認できるけど、“ブラウザ右クリックからのソースの表示” で確認できないというのも面白い。なお、ビルド前後でのファイルの差分は以下の通り。各ページの index.html が生成されているのが分かる。

--- tree.txt	2019-04-28 17:37:14.000000000 +0900
+++ tree-after-build.txt	2019-04-28 17:40:13.000000000 +0900
@@ -35643,6 +35643,70 @@
 │       │       └── readme.md
 │       └── package.json
 ├── package.json
+├── public
+│   ├── 1-f2577b7d9227c6ab20bd.js
+│   ├── 1-f2577b7d9227c6ab20bd.js.map
+│   ├── 404
+│   │   └── index.html
+│   ├── 404.html
+│   ├── app-0d901be2d50c18297369.js
+│   ├── app-0d901be2d50c18297369.js.map
+│   ├── chunk-map.json
+│   ├── component---src-pages-404-js-a1288cb3773ca405fde2.js
+│   ├── component---src-pages-404-js-a1288cb3773ca405fde2.js.map
+│   ├── component---src-pages-index-js-e8da6d35e52eec291ec9.js
+│   ├── component---src-pages-index-js-e8da6d35e52eec291ec9.js.map
+│   ├── component---src-pages-page-2-js-e54da239d5d16d705ff3.js
+│   ├── component---src-pages-page-2-js-e54da239d5d16d705ff3.js.map
+│   ├── icons
+│   │   ├── icon-144x144.png
+│   │   ├── icon-192x192.png
+│   │   ├── icon-256x256.png
+│   │   ├── icon-384x384.png
+│   │   ├── icon-48x48.png
+│   │   ├── icon-512x512.png
+│   │   ├── icon-72x72.png
+│   │   └── icon-96x96.png
+│   ├── index.html
+│   ├── manifest.webmanifest
+│   ├── page-2
+│   │   └── index.html
+│   ├── pages-manifest-65bfa2df20d3a86c24f3.js
+│   ├── pages-manifest-65bfa2df20d3a86c24f3.js.map
+│   ├── static
+│   │   ├── 6d91c86c0fde632ba4cd01062fd9ccfa
+│   │   │   ├── 59139
+│   │   │   │   └── gatsby-astronaut.png
+│   │   │   ├── af144
+│   │   │   │   └── gatsby-astronaut.png
+│   │   │   ├── b5207
+│   │   │   │   └── gatsby-astronaut.png
+│   │   │   ├── d3809
+│   │   │   │   └── gatsby-astronaut.png
+│   │   │   ├── e22c9
+│   │   │   │   └── gatsby-astronaut.png
+│   │   │   └── fdbb0
+│   │   │       └── gatsby-astronaut.png
+│   │   └── d
+│   │       ├── 140
+│   │       │   └── path---index-6a9-0SUcWyAf8ecbYDsMhQkEfPzV8.json
+│   │       ├── 2011440971.json
+│   │       ├── 2417117884.json
+│   │       ├── 285
+│   │       │   └── path---404-html-516-62a-0SUcWyAf8ecbYDsMhQkEfPzV8.json
+│   │       ├── 652
+│   │       │   └── path---dev-404-page-5-f-9-fab-OeR4V5ulE4niY8MxafWSCBBLPtM.json
+│   │       ├── 726
+│   │       │   └── path---page-2-fbc-5a8-0SUcWyAf8ecbYDsMhQkEfPzV8.json
+│   │       ├── 755544856.json
+│   │       └── 820
+│   │           └── path---404-22-d-bce-0SUcWyAf8ecbYDsMhQkEfPzV8.json
+│   ├── styles-0375cdcc38b87565858c.js
+│   ├── styles-0375cdcc38b87565858c.js.map
+│   ├── styles.fc4fa5e094d218207796.css
+│   ├── webpack-runtime-2dde02a234c0aad7ccf9.js
+│   ├── webpack-runtime-2dde02a234c0aad7ccf9.js.map
+│   └── webpack.stats.json
 ├── src
 │   ├── components
 │   │   ├── header.js
@@ -35657,7 +35721,8 @@
 │       ├── 404.js
 │       ├── index.js
 │       └── page-2.js
+├── tree-after-build.txt
 ├── tree.txt
 └── yarn.lock

-4427 directories, 31233 files
+4445 directories, 31280 files

Gatsby 2. サイトテーマの充実度

フレームワークレベルの話なのでもはやテーマとは違うものの、Starter Library として立ち上げを早くするテンプレート的な存在があった。

Starter Library | GatsbyJS

152 Gatsby Starters (filtered) 2019/04/28現在

Gatsby 3. カスタマイズ

大雑把な比較

ここまで完全同一条件での比較ではないものの、ある程度の癖は掴むことが出来た。

Jekyll Hugo Hexo Gatsby
LANG Ruby Go Node Node
RSS 標準組み込み (feed.xml) 標準組み込み (index.xml) plugin plugin
sitemap plugin 標準組み込み (sitemap.xml) plugin plugin
Minify plugin 標準組み込み (hugo --minify) plugin 標準組み込み
Google Analytics tag plugin 標準組み込み (config.toml) 標準組み込み (_config.yml) plugin

(Hugo が有利になるような評価項目したものではないのだけど)Hugoは一通り標準組み込みなのでとっつきやすい印象。もちろん plugin が悪いというわけではないし、むしろ plugin の方が痒いところに手が届くこと多いので、どんどん活用すべきだと思っています。 個人的には、Node (npm) は使い慣れていないので、 npm での plugin 導入を続けていくと package.json はじめコンフィグ類がカオスになっていくので少々辛い。

移行トレンドの観点から

傾向を見ると、“Jekyll から Hugo への移行” という記事が目立つ。生い立ちを考えれば Jekyll の方が先発だと理解しているので、なんらか不満を抱えている(または、新しいものを試してみたい)ユーザーが後発の Hugo へ移行する流れは違和感はない。Jekyll そのものの不満ポイントとしては以下のものが挙げられている。

  • Rubyに慣れていない、Ruby ゆえに(bundleなど)Jekyll が重い
    • 特に Windows 環境メインでは使いづらい
    • jekyll serve --watch しながらテンプレート編集しているときに、極一部の変更確認をしたいだけなのに10秒といった時間を待たされるのは相当なストレスである
  • テーマによってはRubyバージョン依存がある。コンテンツ管理したいだけなのに何故Rubyバージョンに悩まされないといけないのか

wordpress gatsby - Google 検索

WordPressが圧倒的シェアを占めている昨今、「WordPress + なにか」でのキーワード検索からも気づくことがある。例えば Gatsby との関係を調べてみると、WordPress をバックエンドAPIとしてフロントエンドを Gatsby で実装する パターンが興味深い。コンテンツソースを WordPress とする plugin もあるみたい。gatsby-source-wordpress | GatsbyJS

フロントエンド実装の分離を目的としたこの構成はありだと思うのだけど、「WordPressの脆弱性対応に疲れたからフロントエンドをGatsbyにした」という動機には違和感がある。WordPressをバックエンドとして使っている以上、ユーザーからは無制限にアクセスできる環境に露出していると思われるので、WordPress 本体のメンテナンスを不要としたい目的 にはそぐわないはず。露出機会が減るので多少マシという程度ではなかろうか。

所感まとめ

冒頭に示した3つの機能要件に対して、自分がどれを使いたいか見定めるために機能比較をしてみました。

再掲

  1. 標準機能で Markdown にて記事を執筆、メンテできること
  2. ブログデザイン・テンプレートの選択バリエーションがいくつかあること(最初は手っ取り早く移行し終えたいため)
  3. テンプレートのカスタマイズ(≒若干のタグ追加とかのレベル)がわかりやすいこと。構成理解が複雑でないこと

Jekyll, Hugo, Hexo どれを使ってもジェネレータとしては間違いないと思うので、何を優先したいかによるのだろうと思います。今回、以下の判断軸で Hugo を採用してみようと思います。

  • バイナリファイル1個で済む簡潔さ(CI/CDする上で手っ取り早く済みそう)
  • 爆速(CI/CDスピードの向上、手で叩いた時の待ち時間ストレスの低減)
  • プラグイン管理の手間の削減(標準でできることが多い)

テンプレートはそれぞれ似たようなものがあること、およびテーマ数も数百あるので選択肢が多いということから評価軸には含めませんでした。

そもそも静的サイトジェネレーターにする必要ある?定番WordPressの方がプラグインや圧倒的情報量的にメリットじゃない?という見方もあるのですが、やはり脆弱性対応のようなメンテナスコストを1つでも減らせる(気にかける必要がない)メリットを優先します。不正ログインを試みるアクセスがあるだけで気分は良くないですからね。

今回は Markdown ファイルからサイトジェネレートするだけの基本動作確認をしたまでなので、実際にはサイトに載せたいコンテンツの実現をどうするかという技術懸念がいくつか出てくるはずで、引き続き評価が必要になると思っています。

余談

静的サイトジェネレータと呼ばれるものには、最近だとGatbyJSなどのようなSPA型もあることに気づけました。これまで “静的サイトジェネレーター” といえば典型的な html,css,js ファイルの書き出しだと思っていたためです。ただ、SPAだと React をはじめとしたフレームワークのお作法を理解しておく必要があるし、日頃フロントエンド技術スタックに触れていないと手っ取り早さからは遠のいてしまうので、ちょっと辛いかなと思うところです。

一方、(典型的な意味での)静的サイトジェネレータを使っている方の意見を見ると、静的サイトジェネレーターの運用そのものに疲れてしまう という声もありました。それは何故かとうと、Markdownファイルそのものに記載している日付、カテゴリ、タグ情報をファイル内で管理するのが辛いらしい。確かに、たまにカテゴリを変更したいしたいなーって時にファイル1個1個をいじらないといけないのが面倒という気持ちは理解できるところです。たとえば Twitter Cards を投稿毎に Front-Matter 書いて、それ用の画像を用意するのは面倒ですよね。

その他

WordPress からの移行としてみた場合、Hugo へのエクスポーターもあるので移行の補助になりそう。 SchumacherFM/wordpress-to-hugo-exporter: Hugo is static site generator written in golang. Wordpress is a tool for remote access to your server ;-) ❗️Contributions welcome!

静的サイトを配置する対象として Netlify というキーワードも目立っていました。モダンなホスティング的な存在のようで、機をみて試してみたいところ。