😗

Obsidian → Hugo の運用方法

2022/02/20に公開

このノートを作成した経緯

最近, Obsidian というツールを使って, ノートをとっています.
ノートの中には, 公開してもいいかなと思えるものもありました.
そこで, Obsidian でとったノートを楽に公開できるような運用方法について考えてみました.
その最中にいろいろと考慮するポイントがあり楽しかったので, このノートを作成しました.

よりシンプルな方法を Obsidian → Hugo の運用方法 2 で説明しています.

運用方法

↓の3つを使用します.

  1. Obsidian フォルダ: Obsidian vault を格納するフォルダ
    • Obsidian 記法で書かれたマークダウンファイルを格納している
  2. ブログ用フォルダ: Hugo を管理するフォルダ
    • 通常のマークダウン記法で書かれたマークダウンファイルとブログの設定情報を格納するファルダ
  3. Vercel

そして, 公開は↓のステップで行います.

  1. Obsidian でノートをとる
  2. 変換ツールを使用してノートを Obsidian フォルダからブログ用フォルダにエクスポート
  3. Hugo × Vercel で記事を投稿

以下でその詳細を説明します.

Obsidian のフォルダ構成

.
|-- .obsidian
|-- _template
|-- private
    |-- notes
    |-- static
|-- public
    |-- notes
	|-- static
  • .obsidian: Obsidian の各種設定を格納するフォルダ. 自動で作成されます.
  • _template: テンプレートファイルを格納するフォルダ. Obsidian の template プラグインや templater プラグインで使用されます.
  • private/notes: 非公開 (通常) のマークダウンファイルを格納するフォルダ
  • private/static: 非公開の画像などの静的ファイルを格納するフォルダ
  • public/notes: 公開するマークダウンファイルを格納するフォルダ
  • public/static: 公開する画像などの静的ファイルを格納するフォルダ

特徴は, 公開するコンテンツと非公開のコンテンツ, マークダウンファイルと静的ファイルでフォルダ分けしていることです.
マークダウンファイルだけを管理するのであれば, わざわざフォルダ分けしなくても, yaml フロントマターで publish: true/falsedraft: true/false を設定し, 後述するツールを使うことによってエクスポートするファイルを振り分けることも可能です.
ただ, 静的ファイルを同様に扱うのは難しいので, フォルダ分けによって公開/非公開を管理する方針をとっています.
また, このようにフォルダを分けておくことで, 公開用のノートが非公開のノートを参照することによる無効なリンクができてしまったり, 非公開の静的ファイルが間違って公開されてしまったりといったことが起きにくくなるのもメリットだと思います.

また, Obsidian でノートをとるだけであれば, 必ずしもマークダウンファイルと静的ファイルをフォルダ分けしなくてもよいのですが, Hugo ではこれらを別々のフォルダに分けて管理するようになっているので, Hugo へのエクスポートのしやすさも考えて, それにならっています.
さらに, 静的ファイルとマークダウンファイルをフォルダ分けすることで, 私には副次的なメリットがありました.
それは, スマホと PC との間の Obsidian ファイル群の同期にとってです.
私は, Android スマホを使用しており, ファイル群の同期には FolderSync というアプリケーションを使用しています. FolderSync を使うことでスマホのローカルフォルダとクラウドストレージ内のフォルダを同期することができます.
ただ, Obsidian vault 全体を同期すると, スマホのストレージを圧迫することになってしまいます.
そこで, マークダウンファイルを格納する private/notespublic/notes だけを FolderSync で同期することで, 画像ファイルといった容量が大きめのファイルをスマホに保存せずに済むようになります. (スマホで静的ファイルを確認できなくなりますが...)

ちなみに余談ですが, 私はFolderSync では .obsidian も同期していません. これはスマホと PC でプラグインを使い分けられるようにするためです.

これまた余談ですが, 私は private/notespublic/notes 以下はフォルダ分けしていません.
Obsidian では豊富な検索機能とタグ機能が提供されているため, 本文の内容でフォルダ分けする必要性を今は感じていないためです.
ずぼらな自分は, 一階層のフォルダにノートをボンボン放り込み, カテゴリー分けは全てタグで行っています.
基本的には, フォルダ分けは↑で説明したような, ファイルの 種類 や公開/非公開といった 状態 に従うのが自分には合っている気がします.

Hugo のフォルダ構成

.
|-- config
|-- layouts
|-- assets
|-- themes
|-- static
|-- obsidian
    |-- public
        |-- notes
        |-- static
  • config, layouts, assets, themes: デフォルト + テーマのカスタマイズなどに関するもの
  • static: ロゴやファビコンなどブログ特有の静的ファイルを格納するフォルダ (Hugo のデフォルトで静的ファイルを格納するフォルダ)
  • obsidian/public: Obsidian の public フォルダを後述するツールによってエクスポートしてきたフォルダ. 名前の通り, public/static には Obsidian フォルダの public/staticがそのまま入っており, public/notes には Obsidian フォルダの public/notes 内のマークダウンファイルがよしなに変換されたのちに出力されています.

Hugo 側で適切にコンテンツを処理できるようにいくつかの設定変更が必要です.
以下では自分が採用している設定変更を説明します.

Hugo のデフォルトではマークダウンファイルは content ディレクトリ, 静的ファイルは static に格納することになっています.
obsidian/public/notes をマークダウンファイルの格納先, obsidian/public/static を静的ファイルの格納先として Hugo に認識させるため, ↓の設定変更を行います.

contentDir = "obsidian/public/notes"
staticDir = ["static", "obsidian/public/static"]

またリンク周りについても設定変更が必要です.
例えば, 変換後のリンクが [sample](public/notes/abc.md) だった場合, これを URL では qawatake.com/notes/abc のような形式にしたいです.
そのためには, 3つの場所をいじらなければいけません.

  • layouts/_default/_markup/render-link.html
  • lauouts/_default/_markup/render-image.html
  • config/_default/config.toml
{{- $url := urls.Parse .Destination -}}
{{- $scheme := $url.Scheme -}}

<a href="
  {{- if eq $scheme "" -}}
    {{- if strings.HasSuffix $url.Path ".md" -}}
      {{- $destination := strings.TrimPrefix "public/notes" .Destination -}}
      {{- relref .Page $destination | safeURL -}}
    {{- else -}}
      {{- .Destination | safeURL -}}
    {{- end -}}
  {{- else -}}
    {{- .Destination | safeURL -}}
  {{- end -}}"
  {{- with .Title }} title="{{ . | safeHTML }}"{{- end -}}>
  {{- .Text | safeHTML -}}
</a>
{{- $url := urls.Parse .Destination -}}
{{- $scheme := $url.Scheme -}}

<img src="
{{- if eq $scheme "" -}}
{{- strings.TrimPrefix "public/static" .Destination | safeURL -}}
{{- else -}}
{{- .Destination | safeURL -}}
{{- end -}}"
  {{- with .Title }} title="{{ . | safeHTML }}"{{- end -}}
  {{- with .Text }} alt="{{ . | safeHTML }}"
  {{- end -}}
/>
  • render-link.html と同様に public/static を取り除く処理を記載しています.
[permalinks]
	'/' = '/notes/:filename/'
  • ↑の記述を末尾に追加しました
  • この設定がないままだと, obsidian/public/notes/abc.md というパスは qawatake.com/abc という URL になってしまいます.
  • この設定を足すことで, notes という名前空間を作ることができます.

Hugo フォルダ作成の意義

これを読まれている方の中には, わざわざ Obsidian フォルダとブログ用フォルダを分ける必要性を感じられない方もいらっしゃるかもしれません. 私がこの二つを分けた理由は↓の2つです.

  1. 公開するノートをチェックするのに git diffgit status を使いたい.
    しかし, Obsidian フォルダでは毎日それなりの量のファイルが新規作成されているため, フォルダ分けしないと,こうしたコマンドの出力結果が汚れて使いにくくなってしまう.
  2. ブログ固有設定と Obsidian ノートの変更事項をまとめて同じ場所で管理するのが気持ち悪い.

一方で, 中には, 「どうせ Obsidian フォルダとブログ用フォルダを分けるのであれば, Obsidian に頼らず Hugo だけで記事を作成すればいいのでは?というか, note とかはてなブログを使えばいいのでは?」と思われる方もいらっしゃるかもしれません.
私がそうしなかった理由は, あくまでブログは公開できる「ノート」であって, 他のノートと同じように自分が参照できるようにしたいからです.
もし ブログの記事を Obsidian の外部で作成すると, それを自分が参照したくなったとき, 私は Obsidian の外部に情報を探しに行かなくてはいけなくなります. また, ブログの記事に修正を加えるときも, Obsidian の外側に修正を加えにいかなくてはいけなくなります.
私は, このように自分のまとめる情報がいろいろなところにバラバラと散らばるのが気に入りません.
どこに何があったか思い出すのも面倒くさいので, できれば情報は全部 Obsidian に入れておきたいです.

Obsidian フォルダからブログ用フォルダにエクスポート

Obsidian でとったノートをそのまま Hugo で処理できたら嬉しいのですが, 記法の違いなどがあるため, それは叶いません.
例えば, Obsidian には

  • 内部リンク ([[]])
  • 埋め込み (![[]])
  • タグ (#todo),
  • コメント (%% comment %%)

などの記法がありますが, これは通常のマークダウンでは解釈できません.
また, Hugo では yaml フロントマターでタイトルやタグを設定することでブログの構成に役立てることができますが, これらをわざわざ手動で設定するのはめんどくさがりな私には億劫です.

したがって, ↓のような処理を自動的に行ってほしくなります.

  • Obsidian 独自の内部リンクや埋め込みの記法を通常のマークダウンのリンク記法に変換する.
  • 本文中のタグを削除 + フロントマターに設定する.
  • Obsidian 記法のコメントを削除する.
  • 本文中からタイトルを取得してフロントマターに設定する.

これを実現するために, obsdconv というプログラムを作成しました.
例えば,

obsdconv -src /path/to/obsidian_vault -dst /path/to/hugo -link -rmtag -cptag -cmmt -title

を実行することで /path/to/obsidian_vault 内のファイルが↑で述べたように処理されて /path/to/hugo に出力されます.

Image from Gyazo

これまでに述べてきたフォルダ構成に従うと, より具体的なコマンドは↓のようになります.

obsdconv -src /path/to/obsidian_folder -dst /path/to/blog_folder/obsidian -link -rmtag -cptag -cmmt -title

obsdconv はエクスポート用の変換機能を他にも備えているので, 詳細は qawatake - qawatake/obsdconv: convert obsidian markdowns in multiple ways を御覧ください.

Hugo × Vercel で記事を投稿

ここまでで, Hugo の設定変更, マークダウンファイルの変換ができたので, あとは Vercel にデプロイすれば完了です.
ここから先は別に考えることも特にないので省略します.

おまけ: obsdconv を使ったノート管理

↑で述べたとおり, 私は Obsidian フォルダ内のファイルをエクスポートするのに obsdconv というプログラムを使用していますが, ここでは, それ以外の obsdconv の使い方を説明します.

皆さんは Obsidian で新規作成するファイルにどういった名前をつけているでしょうか.
私はずぼらなので, zettelkasten プラグインに任せて 211205-234426 のように現在時刻をそのままファイル名にしています.
しかし, これでは Obsidian のすてきな機能の一つである内部リンク [[]] を非常に使いにくくなってしまいます.
毎回参照したいファイルを全文検索で見つけてきて, ファイル名をコピペして [[211205-234630]] のように記載しなければいけないからです.
obsdconv を使用すれば, zettelkasten プラグインを使いつつ, この面倒を回避することができます.

obsdconv のオプションに -alias があります.
これは, マークダウンファイルの中から H1 の内容を見つけてそれを フロントマターの aliases フィールドに追加してくれるというものです.
例えば, ↓のようなマークダウンファイルがあるとします.

# This is a sample file #sample

↓のコマンドを実行します.

obsdconv -src sample.md -dst sample.md -alias

変換後のファイルは↓のようになります.

---
aliases:
- This is a sample file
---
# This is a sample file #sample

このように, H1 の内容から余分なタグなどを取り除いてできた文字列 (ここでは This is a sample file) がエイリアスとして設定されるわけです.
エイリアスに This is a sample file があれば, 他のファイルでこのファイルを参照したいときに, [[sample などと入力すれば, [[211205-235433 | This is a sample file]] が自動補完されるはずです.
obsdconv と zettelkasten を使えば, 「ファイル名に日本語やスペースや記号を入れたくないけど, ファイル名どうしようかな」みたいな余計なことは考えずに, 内部リンクの恩恵を受けられるわけです.

Discussion