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 |
画像を挿入したい
Markdownを使う方法

<p><img src="画像のパス" alt="alt属性" /></p>
Shortcodesを使う方法
- 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 }} – {{ .Site.Title }} {{ end }} {{ define "main" }} <h1>{{ .Title }}</h1> {{ .Content }} {{ end }}
uglyurls を使いたい
- ドキュメントの 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 を使いたい
- デフォルトでは “canonifyurl = false” になっている
- そのため、文書中の相対URL (“/css/main.css”など)は、そのまま使われる
- “canonifyurl = true” にすると、baseurl が付いた形(“$BASEURL/css/main.css”)になる
- これは “false” でよいと思う
relativeurl を使いたい
- これも、デフォルトでは “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 | ナビゲーション・テンプレート |
画像置き場
- 画像は /static 以下に置く。サブディレクトリを作ってもよい
/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", ...)
og:description
<meta property="go:description" content="{{ with .Description }} {{ . }} {{ else }} {{if .IsPage}} {{ .Summary }} {{ else }} {{ with .Site.Params.description }} {{ . }} {{ end }} {{ end }} {{ end }}" />
- .Description があれば .Description
- ない場合、.IsPage ならば .Summary
- それ以外の場合、.Site.Params.description があればそれ
og:type
<meta property="og:type" content="{{ if .IsPage }}article{{ else }}website{{ end }}" />
- .IsPage ならば article
- それ以外 ならば website
og:url
<meta property="og:url" content="{{ .Permalink }}" />
- .Permalink
og:image
{{ with .Params.images }} {{ range first 6 . }} <meta property="og:image" content="{{ . | absURL }}" /> {{ end }} {{ end }}
- .Params.images があれば、最初の6つを使う
og:updated_time
{{ if not .Date.IsZero }} <meta property="og:updated_time" content="{{ .Date.Format "2006-01-02T15:04:05-07:00" | safeHTML }}"/> {{ end }}
og:audio
{{ with .Params.audio }} <meta property="og:audio" content="{{ . }}" /> {{ end }}
og:locale
{{ with .Params.locale }} <meta property="og:locale" content="{{ . }}" /> {{ end }}
og:site_name
{{ with .Site.Params.title }} <meta property="og:site_name" content="{{ . }}" /> {{ end }}
og:video
{{ with .Params.videos }} {{ range .Params.videos }} <meta property="og:video" content="{{ . | absURL }}" /> {{ end }} {{ end }}
Facebook関係のOGP
{{ 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 }}`)
Twitter関係のOGP
- 使ったことないけれど 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 }}
- .Params.images があれば、summary_large_image にして .Params.images[0] を twitter:image:src にする
- ない場合は 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 }}`)