Hugo

  • Go言語(Golang)で書かれた静的サイトジェネレータ
  • Markdownで書かれた文書をウェブサイトに変換
  • ずば抜けて速い(らしい)
  • 生成されるのは静的サイトなので、CGIとかPHPが動かないサーバでも使える。KEKのウェブサーバにアップロードして使うことができる。

使い方

ウェブサイト作成の方針

  • HTML5 + CSS3に準拠する
  • 外部のテーマを使用し、ところどころカスタマイズする
  • 外部テーマの一部を変更する場合は、部分テンプレートをローカルに作成する
  • 自分用のショートコードは、ローカルに作成する。テーマを変更した際に使えなくなるため。

サイトを新規に作成したい

$ hugo new site /tmp/hugoTest                                                                                                                              

  • 新規サイトを作成するときは “hugo new site サイト名” を実行する
  • サイト名 のディレクトリ以下に必要なファイル/ディレクトリが自動生成される
  • ついでに、以下のメッセージも表示される

Congratulations! Your new Hugo site is created in "/private/tmp/hugoTest".

Just a few more steps and you're ready to go:

1. Download a theme into the same-named folder.
   Choose a theme from https://themes.gohugo.io/, or
   create your own with the "hugo new theme <THEMENAME>" command.
2. Perhaps you want to add some content. You can add single files
   with "hugo new <SECTIONNAME>/<FILENAME>.<FORMAT>".
3. Start the built-in live server via "hugo server".

Visit https://gohugo.io/ for quickstart guide and full documentation.

  • 自動生成されるファイルは以下のように6つのディレクトリ(空ディレクトリ)と1つのファイルになる

$ tree hugoTest/
hugoTest/
├── archetypes/
├── config.toml
├── content/
├── data/
├── layouts/
├── static/
└── themes/

6 directories, 1 file

テーマをインストールしたい

  • git clone する
  • git submodule add してもよい

$ cd /tmp/hugoTest/themes/
$ git clone ...

サイト全体の設定をしたい

  • config.toml という名前の設定ファイルを作成・編集する

$ emacs config.toml

  • サイト全体の設定値を確認したいときは “hugo config” を実行する
  • ドバっと結果が表示されるので、grep などにパイプして確認するとよい

$ hugo config
$ hugo config | ag PATTERN

ローカルでテストしたい

$ hugo server

  • ページのレンダリングが終わったあとのメッセージに、ローカルサーバのアドレスが出てくるので、それをブラウザで開いて確認する
  • 「localhost:1313/ほにゃらら」になっているはず。「ほにゃらら」の部分は baseurl の設定に依る。

サイトを生成したい

  • サイトに必要な全てのファイルが揃っている場合 “hugo” と入力するだけで、ウェブサイトが生成される
  • テスト用を生成する際は、別のディレクトリを指定することもできます

$ hugo

$ hugo -d TEST

骨組みとなるテンプレートを作成したい

  • v0.17から実装された「ブロックテンプレート機能」を使用する
  • 「layouts/_default/baseof.html」を作成・編集する
  • ものすごくシンプルに骨組みを作ると、以下のようになるはず
    • partial は 部分テンプレートを読み込むための書き方
    • block は ブロックテンプレートを読み込むための書き方

<!doctype html>
<html lang="ja">
    <head>
        {{ partial "head" . }}
    </head>
    <body>
        <nav>
        {{ partial "navbar" . }}
        </nav>
        <main>
        {{ block "main" . }}
        {{ end }
        </main>
        <aside>
        {{ partial "sidebar" . }}
        </aside>
        <footer>
        {{ partial "footer" . }}
        </footer>
    </body>
</html>

  • 書き方のポイント
    • ヘッダ(head)やナビゲーション(nav)、サイドバー(aside)、フッタ(footer)という全てのページに共通する部分は、部分テンプレートを読み込む形で書いておくとよい
    • ページの種類ごと(single or list)に変わる部分(main)は、ブロックテンプレートで書いとくとよい
  • 単一ページでは main ブロックでコンテンツを表示させる

{{ define "main" }}
<article>
  {{ .Content }}
</article>
{{ end }}

  • リストページでは、コンテンツ一覧を表示するようにする
  • list-card は マテリアルデザインのカード要素みたいな、自分でカスタマイズした部分テンプレートを読み込んでいる感じ

{{ define "main" }}
{{ range .Paginator.Pages }}
{{ partial "list-card" . }}
{{ end }}
{{ end }}

本文をレンダリングしたい

  • 本文は、フロントマターより下の部分のこと

{{ .Content }}

ナビゲーションを作成したい

  • ナビゲーションは、全てのページで使うので、部分テンプレートとして作成する
  • 外部テーマにも、必ず該当する部分テンプレートがあるので、それを参考にする

<header>
    <div class="header__title">
        <h1>{{ .Title }}</h1>
    </div>
    <nav>
        <ul>
            {{ partial "menus" . }}
        </ul>
    </nav>
</header>    

多言語サイトを作成したい

v0.17から標準で多言語サイトがサポートされるようになった

テンプレートの関係を知りたい

生成されるページと、そのテンプレートについてまとめてみた

トップページを作成したい

  • トップページの作成には /layout/index.html が必要
  • content/index.md ではない

新規ページを作成したい

  • 新規ページを作成するときは “hugo new ファイル名.md” を実行する
  • ファイル名.md は content ディレクトリの下に作成される
  • ファイル名.md には サブディレクトリも指定できる
  • ファイル名.md の作成先は、config.toml の contentdir で変更できる

$ cd MySite
$ hugo new post/first-post.md
$ ls content/

  • 例として「post/」以下にコンテンツを作成した場合を考えてみた
  • コンテンツには「layouts/_default/single.html」が適用される
  • コンテンツのファイル名(FILENAME.md)は、ディレクトリ名(FILENAME/index.html)に変化する。だだし、 index.md は index.html になる
  • これは、セクション(/post/)の中にサブディレクトリ(/post/2016/など)を作った場合に便利
  • サブディレクトリ(/post/2016/)の内容一覧は自動生成されないので、手動で管理することにはなるが、簡易な一覧を作成した場合は重宝するはず
  • 自動生成したい場合は、テンプレートをいじればよい(はず)
hugo new したとき hugoしたとき 適用されるテンプレート
content/post/first-post.md public/post/first-post/index.html layouts/_default/single.html
public/post/index.html layouts/_default/list.html
content/post/2016/first-post.md public/post/2016/first-post/index.html layouts/_default/single.html
content/post/2016/index.md public/post/2016/index.html layouts/_default/single.html

テーマを一時的に変更したい

  • テーマを一時的に変更したいときは “hugo -t テーマ名” を実行する
  • テーマ名 はあらかじめ themes/テーマ名 に作成しておく

$ hugo server -t THEMENAME

リダイレクトを設定したい

  • 旧サイトからHugoに移行した場合とかには、旧URLにリダイレクトを設定する必要がある
  • そんなときはalias機能を使うとよい
  • front matter の aliases セクションに、ウェブルート(baseurl?)からのパスを書いておく。aliases にはリストで記述することができる
  • aliasesに設定したパスにファイルが作成され、その中にリダイレクト内容が書き出される
  • 注意点は2つある
    • 同じドメイン内でしか使えない(外に飛ばそうとするとエラーになる)
    • 上書き確認はない。既存のファイルを書き換えたくない場合は、設定しちゃだめ

+++
...
aliases = [
    "/news/comet-cdc-carry-in/index.html"
    "/articles/comet-cdc-carry-in/index.html" 
    ]
+++    

メニューバーを作成したい

  • メニューに追加するには front matter に書く方法と、config.toml に書く方法がある
  • ここでは config.toml に書く方法を書いておく
  • 詳細は以下のドキュメントを参照
  • メニューは入れ子にすることもできる
    • 親メニューには identifier を設定する
    • 子メニューには parent を設定する
    • リンク名は name で設定する
    • リンク先は url で設定する
    • 順番は weight で設定する
    • アイコンは pre で設定する

[[menu.main]]
    name = "about hugo"
    pre = "<i class='fa fa-heart'></i>"
    weight = -110
    identifier = "about"
    url = "/about/"
[[menu.main]]
    name = "getting started"
    pre = "<i class='fa fa-road'></i>"
    weight = -100
    url = "/getting-started/"

URLを設定したい

  • 個別のURLは front matter で指定することができるが、それはとても面倒くさい
  • セクションごとにURLを設定できる Permalink がある
  • サイト設定なので “config.toml” に書く

[permalinks]
post = "/:section/:year/:month/:filename/"
report = "/:section/:year/:month/"
cafe = "/:section/:year/:month/"

  • 上の設定だと以下のようになる
  • YYYY, MM は front matter の date から拾ってくれる
  • エイリアスがどうなるのかよく分かってない・・・
セクション コンテンツファイル 公開ファイル
post content/post/FILENAME.md public/post/YYYY/MM/FILENAME/index.html
report content/report/FILENAME.md public/report/YYYY/MM/index.html
cafe content/cafe/FILENAME.md public/cafe/YYYY/MM/index.html
それ以外 content/FILENAME.md public/FILENAME/index.html

画像を挿入したい

![alt属性](画像のパス)

<p><img src="画像のパス" alt="alt属性" /></p>

  • Markdownを使う方法だと、キャプションを入れるなどのコントロールができない
  • Shortcodesを自作する方がカスタマイズ性が高い

ブロックテンプレートを使いたい

  • v0.16から実装された機能
  • ベースとなるテンプレート(“_default/baseof.html”)を穴埋め式で作成しておき、ページ生成のときに読み込まれる従来のテンプレート(“_defaults/single.html”, “_defaults/list.html” など)でその穴を埋めるように定義する方式
  • 従来のテンプレートで重複して書いているナビゲーション、フッタなどの部分テンプレートの読み込みを排除することでができ、テンプレートがより読みやすくなるはず
  • とりあえず公式ドキュメントのサンプルを持ってきた
  • “baseof.html” の “block” で書かれた部分(“title” と “main”)の部分が、穴埋め部分
  • “list.html” の “define” で書かれた部分が、穴埋めの回答。この場合は “main” の回答。
  • “single.html” のように複数の回答を用意することもできる

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>{{ block "title" . }}
      <!-- Blocks may include default content. -->
      {{ .Site.Title }}
    {{ end }}</title>
  </head>
  <body>
    <!-- Code that all your templates share, like a header -->

    {{ block "main" . }}
      <!-- The part of the page that begins to differ between templates -->
    {{ end }}

    <!-- More shared code, perhaps a footer -->
  </body>
</html>

{{ define "main" }}
  <h1>Posts</h1>
  {{ range .Data.Pages }}
    <article>
      <h2>{{ .Title }}</h2>
      {{ .Content }}
    </article>
  {{ end }}
{{ end }}

{{ define "title" }}
  {{ .Title }} &ndash; {{ .Site.Title }}
{{ end }}
{{ define "main" }}
  <h1>{{ .Title }}</h1>
  {{ .Content }}
{{ end }}

  • ドキュメントの URLs のところも参考までに
  • これは config.toml の “uglyurls” で、ファイル名変換をPrettyURL方式にするか、UglyURL方式にするか、設定することができる
  • デフォルトでは “uglyurls = false” になっているため FILENAME.md から FILENAME/index.html が生成される(Pretty URL)
  • “uglyurls = true” にすることで、FILENAME.html に生成することもできる (Ugly URL)
  • ページ単位での設定は、前述した front matter の url で指定する
  • デフォルトでは “canonifyurl = false” になっている
  • そのため、文書中の相対URL (“/css/main.css”など)は、そのまま使われる
  • “canonifyurl = true” にすると、baseurl が付いた形(“$BASEURL/css/main.css”)になる
  • これは “false” でよいと思う
  • これも、デフォルトでは “relativeurls = false”
  • だけど、外から持ってきたテンプレートを使うときなどは “relativeurls = true” にする必要がある
    • hugo server するだけならば、いらない
    • BaseURLがルートディレクトリの場合は、不要かも
    • BaseURLがサブディレクトリを含む場合(www2.kek.jp/ipns/ など)は必要だと思う

テンプレートの編集

  • ドキュメント : Templates Overview - Hugo
  • サイトジェネレータの基本はテンプレートの編集にある
  • このテンプレートの集まりが「テーマ」である
  • Hugoにはテンプレートの 部分的override 機能があるので、ベースとなるテーマを選択し、部分的に自分の用途に合わせて改造するのがよいと思う
/layouts/ テンプレート置き場
/layouts/partials/ 部分テンプレート置き場
  • 日本語でいう「トップページ」は layouts/index.html に作成する
/layouts/index.html ホームページ・テンプレート
  • ヘッダー/フッターは全てのページに共通する要素
  • 部分テンプレートとして作成し、他のテンプレートに流しこめるようにしておく
  • Google analytics などの解析ツールの<script>タグも部分テンプレートにできる
  • ナビゲーション(<nav>タグ)も部分テンプレートにでき、ここに埋め込むリンクは /config.toml で設定可能
/layouts/partials/header.html ヘッダ・テンプレート
/layouts/partials/footer.html フッタ・テンプレート
/layouts/partials/analytics.html 解析コード・テンプレート
/layouts/partials/menu.html ナビゲーション・テンプレート

/content と /static のディレクトリの使い方

  • 上の要望は、例えばあるブログ・コンテンツ(=mdファイル + 画像ファイル + …)は同じディレクトリで管理したいというもの
  • Hugo開発者とユーザの間で「content」の意味の捉え方に違いがあったために生じた不満
  • そのうち実装されるかもだが、いまのところは画像を /static に置いとくのが無難
/content プロセスしたいファイルを置く場所
/static プロセスしない(=そのままコピー)ファイルを置く場所

Shortcodes

参考になりそうなDiscussion

興味のある Hugo ディスカッション

    • 質問者は Hugoのバグだと書いている
    • とりあえず relativeurls と canonifyurls を True にして解決したと言っている
    • んで、この対処法は確かにそうで、自分のHugoサイトでもそうしている + テンプレートの方にも修正を加えていて、ナビゲーションなどのリンクは絶対パス、その他は相対パスを使うようにしている
    • Hugoで多言語をnativeに扱えるようにしてる開発ブランチ
    • masterにマージされたが、Homebrewなどではまだリリースされていない
    • 手元では、既存の方法で多言語化するシステムを構築したので、これを使うかはしばらく様子見
    • ただし、今の方法だと、2つのサイトを同時に hugo watch できないので、そのうちこれを取り入れる予定

デプロイを自動化したい

  • Hugo自体にはデプロイするためのコマンドがないので、Makefile を使って代用します

OGPを使いたい

  • Open Graph protocol を設定したい
  • hugo/tpl/template_embedded.go の EmbedTemplates()関数 の AddInternalTemplate()メソッド(?)に書いてある

// Add SEO & Social metadata
t.AddInternalTemplate("", "opengraph.html", ...)
...
t.AddInternalTemplate("", "twitter_cards.html", ...)

<meta property="go:description"
      content="{{ with .Description }}
                   {{ . }}
               {{ else }}
                   {{if .IsPage}}
                       {{ .Summary }}
                   {{ else }}
                       {{ with .Site.Params.description }}
                           {{ . }}
                       {{ end }}
                   {{ end }}
               {{ end }}" />

  1. .Description があれば .Description
  2. ない場合、.IsPage ならば .Summary
  3. それ以外の場合、.Site.Params.description があればそれ

<meta property="og:type" content="{{ if .IsPage }}article{{ else }}website{{ end }}" />

  1. .IsPage ならば article
  2. それ以外 ならば website

<meta property="og:url" content="{{ .Permalink }}" />

  1. .Permalink

{{ with .Params.images }}
    {{ range first 6 . }}
        <meta property="og:image" content="{{ . | absURL }}" />
    {{ end }}
{{ end }}

  1. .Params.images があれば、最初の6つを使う

{{ if not .Date.IsZero }}
<meta property="og:updated_time" content="{{ .Date.Format "2006-01-02T15:04:05-07:00" | safeHTML }}"/>
{{ end }}

{{ with .Params.audio }}
<meta property="og:audio" content="{{ . }}" />
{{ end }}

{{ with .Params.locale }}
<meta property="og:locale" content="{{ . }}" />
{{ end }}

{{ with .Site.Params.title }}
<meta property="og:site_name" content="{{ . }}" />
{{ end }}

{{ with .Params.videos }}
    {{ range .Params.videos }}
        <meta property="og:video" content="{{ . | absURL }}" />
    {{ end }}
{{ end }}

{{ if .IsPage }}
    {{ range .Site.Authors }}
        {{ with .Social.facebook }}
            <meta property="article:author" content="https://www.facebook.com/{{ . }}" />
        {{ end }}
        {{ with .Site.Social.facebook }}
            <meta property="article:publisher" content="https://www.facebook.com/{{ . }}" />
        {{ end }}
        <meta property="article:published_time" content="{{ .PublishDate }}" />
        <meta property="article:modified_time" content="{{ .Date }}" />
        <meta property="article:section" content="{{ .Section }}" />
        {{ with .Params.tags }}
            {{ range first 6 . }}
                <meta property="article:tag" content="{{ . }}" />
            {{ end }}
        {{ end }}
    {{ end }}
{{ end }}

<!-- Facebook Page Admin ID for Domain Insights -->
{{ with .Site.Social.facebook_admin }}
    <meta property="fb:admins" content="{{ . }}" />
{{ end }}`)

  • 使ったことないけれど config.toml には以下のように書いておく
  • アカウント名に @ は付けなくて良い

[social]
twitter = アカウント名
twitter_domain = 

[authors]
twitter = アカウント名

twitter:card

{{ with .Params.images }}
  <!-- Twitter summary card with large image must be at least 280x150px -->
  <meta name="twitter:card" content="summary_large_image"/>
  <meta name="twitter:image:src" content="{{ index . 0 | absURL }}"/>
{{ else }}
  <meta name="twitter:card" content="summary"/>
{{ end }}

  1. .Params.images があれば、summary_large_image にして .Params.images[0] を twitter:image:src にする
  2. ない場合は summary にする

twitter:title

<!-- Twitter Card data -->
<meta name="twitter:title" content="{{ .Title }}"/>

twitter:description

<meta name="twitter:description"
      content="{{ with .Description }}
                   {{ . }}
               {{ else }}
                   {{if .IsPage}}
                       {{ .Summary }}
                   {{ else }}
                       {{ with .Site.Params.description }}
                           {{ . }}
                       {{ end }}
                   {{ end }}
               {{ end }}"/>

twitter:site

{{ with .Site.Social.twitter }}
<meta name="twitter:site" content="@{{ . }}"/>
{{ end }}

twitter:domain

{{ with .Site.Social.twitter_domain }}
<meta name="twitter:domain" content="{{ . }}"/>
{{ end }}

twitter:creator

{{ range .Site.Authors }}
  {{ with .twitter }}
      <meta name="twitter:creator" content="@{{ . }}"/>
  {{ end }}
{{ end }}{{ end }}`)