Obsidianでは、ファイル名を日本語で付けています。

そのままQuartzで公開しても特に困るわけではないのですが、URLを見ると日本語ファイル名がそのまま入るので、かなり長くなります。

記事タイトルやファイル名は後から変えることもあるので、できれば公開URLはそれとは切り離しておきたい。そこで、Front Matterのpermalinkを使えば簡単に固定URLにできるだろうと思いました。

ところが、Quartz v5で実際に試してみると、私が想像していた動きとは少し違いました。

permalinkで指定したURL自体は作られるのですが、そこに記事本体が置かれるわけではなく、元のファイルパスから作られたURLへのredirectとして動いていました。

そこで、Vault側のファイル名やフォルダ構成はそのままにして、公開するときだけpermalinkを記事URLとして使えるようにする方法を組んでみました。

Warning

この記事はQuartz v5.0.0で確認した内容です。
Quartzの内部仕様変更により、そのまま動かなくなる可能性があります。
配布用ツールとしての保守は行いません。

permalinkを入れれば終わりだと思っていた

最初に考えたのは単純で、Front Matterにこんな感じでpermalinkを書けばいいだろう、というものでした。

permalink: 2024040414012730

これならファイル名が日本語でも、公開URLは、

/2024040414012730

のようにできるだろうと。

ところが実際には、記事本体は元のファイルパスから作られたURLにあり、permalink側にはそこへ転送するためのページが作られていました。

イメージとしてはこんな感じです。

/2024040414012730
        ↓ redirect
/日本語フォルダ/日本語記事

これでも「ファイル名を変えても変わらない入口」という意味では使えます。

ただ、私が欲しかったのはredirect用のURLではなく、permalinkそのものを記事本体のURLにすることでした。

本当にQuartz標準の動作なのか確認した

最初は自分の設定やカスタマイズが原因かもしれないと思っていました。

そこで後から、現在のQuartz v5を使ったまっさらな環境を作り、同じことが起きるか再現テストもしています。

結果は同じでした。

記事本体のURLはファイルパス由来で、permalinkはその記事へのalias redirectとして生成されます。

元ファイルを別のフォルダへ移動したり、ファイル名を変えたりすると、記事本体のURLもredirect先も変わりました。

さらに確認したところ、sitemap、RSS、検索用のcontent index、OG/Twitter用のURLも、記事本体と同じくファイルパス由来のURLを使っていました。

少なくともQuartz v5.0.0では、私が考えていた「ファイル位置とは無関係な記事本体の固定URL」にはなっていませんでした。

この件はQuartzへIssueを出しています。

Issue #2572 - v5: permalink does not provide a path-independent page URL as documented

その後、この記事で紹介している私の回避方法についてもIssueへ追記しました。

今後Quartz側で何らかの対応が入れば、この自前実装自体が不要になる可能性もあります。

それなら公開するときだけファイル名を変える

最終的には、考え方を少し変えました。

Vaultにある元ファイルは触りません。

例えば、

Work/
└─ Quartz/
   └─ 日本語記事.md

というファイルがあり、

permalink: 2024040414012730

となっていたら、Quartzへ渡す公開用のコピーだけを、

content/
└─ 2024040414012730.md

として作ります。

元の日本語ファイルをrenameしたり、ルートディレクトリへ移動したりしているわけではありません。

公開処理の途中で別のコピーを作り、そのコピーのファイル名にpermalinkを使っています。

Quartzは入力されたMarkdownのファイルパスから記事URLを作るので、公開用コピーがこの名前なら、記事本体のURLも、

/2024040414012730

になります。

公開用コピーからはpermalink自体を削除しています。

残したままだと、QuartzのAliasRedirectsが同じrouteに対してredirect pageを生成してしまうためです。

処理の流れだけ抜き出すと、かなり単純です。

Vaultの元記事
        ↓
permalinkを取得
        ↓
content/<permalink>.md の公開用コピーを作る
        ↓
公開用コピーからpermalinkを削除
        ↓
通常どおりQuartzでbuild

Quartz本体を改造しているわけではありません。

Quartzから見れば、最初から2024040414012730.mdというMarkdownがcontentに置かれているだけです。

元ファイルを移動してもURLは変わらない

この方式なら、Vault側で、

Work/Quartz/日本語記事.md

を、

Memo/Quartzについて.md

のように変更しても、Front Matterのpermalinkを変えない限り、公開時には同じ、

content/2024040414012730.md

が作られます。

そのため記事URLも、

/2024040414012730

のままです。

再現テストでは、sitemap、RSS、検索用index、OG/TwitterのURLについても、このIDを使ったURLになることを確認しました。

私が欲しかったのはこの動きでした。

でも、そのままだとExplorerの階層が消える

URLについてはこれで解決しましたが、別の問題が出てきます。

Quartzから見ると、公開用の記事は全部こんな状態になります。

content/
├─ 2024040414012730.md
├─ 2024050120134521.md
├─ 2025050315423012.md
└─ ...

これでは、元のVaultでどのフォルダに入っていた記事なのかQuartz側からは分かりません。

この物理構造をそのままExplorerに使うと、せっかくVaultで整理しているフォルダ階層が消えてしまいます。

そこで、公開用コピーを作るときに、元ファイルの位置も別の情報として残すようにしました。

例えば、

id: 2024040414012730
displayTitle: 日本語記事
logicalFolder: Work/Quartz

といった情報です。

URLを決めるための物理的なファイル位置と、画面上で見せるための論理的な階層を分けています。

Explorerでは元のVault階層を再現する

Explorerについては、独自のExplorerを一から作っているわけではありません。

Quartz標準のExplorerをそのまま使い、渡すツリーだけを調整しています。

そのため、実際の公開ファイルは、

content/2024040414012730.md

でも、Explorer上では、

Work
└─ Quartz
   └─ 日本語記事

と、元のVaultと同じ階層で表示されます。

記事本体のURLは、

/2024040414012730

のままです。

Breadcrumbsについても同じ考え方で、実際のURLはIDのまま、表示上は元のVault階層になるようにしています。

つまり今の構成では、

  • URLを決めるための物理path
  • ExplorerやBreadcrumbsで見せる論理path

を別々に扱っています。

ここまで来ると私の環境固有の実装も増えてきますが、URLを固定するだけならExplorerやBreadcrumbsの対応は必須ではありません。

16桁の数字である必要はない

私のサイトでは、

2024040414012730

のようなIDをpermalinkとして使っています。

ただし、これはQuartzの仕様ではありません。

16桁でなければ動かないわけでもなく、Quartzがこの形式を要求しているわけでもありません。

これは以前から使っていた私自身の運用ルールです。

必要なのは、少なくとも公開する記事同士で値が重複せず、URLとして問題のない値を使うことです。

IDの決め方まで同じにする必要はありません。

私の環境ではもう少しいろいろやっている

実際の公開処理では、このほかにも、

  • 公開対象の記事だけを抽出する
  • permalinkの重複やURL衝突を検査する
  • WikiLinkを公開先のURLへ変換する
  • 公開できない添付ファイルがあれば停止する
  • 元のVault階層を保持する
  • ExplorerやBreadcrumbsへその階層を反映する

といった処理も入れています。

そのため、現在使っているスクリプトをそのまま公開しても、他のQuartz環境でそのまま使えるものにはなっていません。

完成した汎用スクリプトまでは載せていません。

その代わり、どういう流れで実装したかはこの記事に書いています。私自身もChatGPTやCodexにかなり頼って組んだので、同じように環境に合わせて作ってもらうのもありだと思います。

なお、今回の仕組み自体はNetlify専用ではありません。

私はNetlifyを使っていますが、必要なのはQuartzをbuildする前に公開用のコピーを作ることです。

GitHub Pagesなど別の方法で公開していても、Quartz buildの前に同じprojection処理を入れられる環境であれば、考え方は変わりません。

今後、公式対応されるかもしれない

今回の件はIssueへ報告し、自分の環境で行った回避方法についてもコメントで共有しました。

ただし、これはあくまで現在のQuartz v5.0.0に対して私が使っている方法です。

Quartz本体でpermalinkが記事本体の固定URLとして扱われるようになれば、少なくとも今回のprojection処理は不要になる可能性があります。

そのため、今回使っているスクリプトを汎用ツールとして配布したり、今後のQuartzの変更に合わせて保守したりする予定はありません。

自分の環境については、そのとき必要になればまたChatGPTやCodexに相談しながら直していくつもりです。

まとめ

日本語ファイル名そのものをやめれば、URLを短くするのは簡単です。

でも私は、Obsidianではこれまでどおり日本語のファイル名やフォルダ構成を使いたい。

一方で、公開URLはファイル名とは切り離しておきたい。

その二つを両立するために、

Vaultでは人間向けの名前を使い、公開するときだけpermalinkを記事の物理URLとして使う

という形にしました。

少し遠回りにはなりましたが、ファイル名やフォルダを後から変更しても公開URLを気にしなくてよくなったので、今のところはこの形で落ち着いています。

関連記事