やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

zola URLの罠

 page.html, section.htmlではルート相対パスを使おう。もしファイル相対パスにするとURL末尾に/をつけたら参照できなくなってしまう。

💀罠

 静的サイトジェネレータzolaは、おなじページを以下のような異なるURLで参照できてしまう。

  • {base_url}/about
  • {base_url}/about/

 このときファイル相対パスだと、一階層分../の違いが生じてしまう。よってCSSを参照するには以下のようなコードにせねばならない。

<!-- {base_url}/about -->
<link rel="stylesheet" href="css/style.css">
<!-- {base_url}/about/ -->
<link rel="stylesheet" href="../css/style.css">

 だが、これにはいくつもの問題がある。

  • 冗長である
  • すべての深さを網羅せねばならない(メンテ大変。バグの温床)
  • 必ずエラーになる(どれかひとつしか有効でない。それ以外はエラー。ブラウザの開発ツールにあるコンソール参照)

⭕対処

 よって、zolaではpage.html, section.htmlのようなテンプレートでURLを指定するとき、ルート相対パスをもちいるべきだ。なぜならそれらテンプレートは、異なる深さの階層で参照されうるから。

<link rel="stylesheet" href="/css/style.css">

 基礎として、HTMLでURL指定するときは以下の要点を理解しておく必要がある。

パス表記方法 HTML内パス URLが{base_url}/about/時のHTML内パス
絶対パス {base_url}/css/style.css {base_url}/css/style.css
ルート相対パス /css/style.css {base_url}/css/style.css
ファイル相対パス css/style.css
./css/style.css
../css/style.css
{base_url}/about/css/style.css
{base_url}/about/css/style.css
{base_url}/css/style.css

 先頭に/があるかないかで参照パスが変わってしまう。これが気づきにくいせいで罠になる。文字としても小さくて気づきにくい。また、URLが{base_url}のときはおなじ参照パスである。なのにルートよりも下のパス、たとえば{base_url}/about/になると違う参照パスになる。これもまた気づきにくい一因だ。

 詳しくみていこう。

使い分け

方法 動機
絶対パス 異なる階層から参照されうる。
ルート相対パス ドメインを省略したい。異なる階層から参照される。
ファイル相対パス パスも省略したい。同じ階層からしか参照されない。
<link rel="stylesheet" href="https://example.com/css/style.css"><!--絶対パス-->
<link rel="stylesheet" href="/css/style.css"><!--ルート相対パス-->
<link rel="stylesheet" href="css/style.css"><!--ファイル相対パス-->
<link rel="stylesheet" href="./css/style.css"><!--ファイル相対パス-->
<link rel="stylesheet" href="../css/style.css"><!--ファイル相対パス-->

 zolaではルート相対パスを使うべき。なぜならzolaのページは必ず、末尾に/がつくときとつかないときの2種類ある。よって異なる階層から参照されることを想定したパス指定をすべきだ。

❌ダメ

<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="./css/style.css">

 参照するURLの先頭になにもつけないか./をつけることで、ファイル相対パスになる。

 ファイル相対パスは、現在のHTMLファイルが存在するディレクトリからの相対パスだ。このとき、このHTMLがテンプレートであり、異なる階層にあるページから参照される場合、CSSを参照できなくなることがある。

 以下コードのように全パターンを網羅すれば参照できなくはない。

<!-- {base_url}/about -->
<link rel="stylesheet" href="css/style.css">
<!-- {base_url}/about/ -->
<link rel="stylesheet" href="../css/style.css">

 けれど面倒だし、抜けや漏れが生じうる。また、必ずどれかひとつしか参照できず、それ以外はエラーになってしまう。ブラウザの開発ツールでコンソールを開けばわかる。それは好ましくない。

 よって、複数の異なる階層から参照されるファイルは、絶対パスで指定するか、またはルートからの相対パスドメイン省略)で指定すべきである。

⭕OK!

<link rel="stylesheet" href="/css/style.css">

 参照するURLの先頭に/をつけることで、ルート相対パスになる。

 ルート相対パスは、ルートディレクトリ(ドメイン)からの相対パスだ。ルートとはURLでいうと{base_url}だ。config.tomlbase_urlキーで指定された値である。デフォルトはhttps://example.com。ローカルサーバで起動したならhttp://127.0.0.1:1111。またはzola buildしたときのpublic/直下だ。

 ルート相対パスならどの階層のURLから参照されたとしても、おなじ表記でおなじパスを示すことができる。

HTMLにおけるURL参照

 これはzolaのせいではなくHTMLやURLの参照仕様だと思われる。そこで、あらためてHTMLにおけるURL参照について整理してみた。

 {base_url}=https://example.comとする。このときstyle.cssファイルへのURL指定方法は以下の4通りある。

パス 意味
style.css 現在HTMLが存在するディレクトリからの相対パス
./style.css 現在HTMLが存在するディレクトリからの相対パス
/style.css {base_url}からの相対パス
https://example.com/style.css 絶対パス

 絶対パスは明確だからわかりやすい。これを使えば今回の罠にかかることもない。ただ、ドメイン名が変更されたりしたときなどで変更が面倒になる。なので相対パスを使いたい。だが、その相対パスが厄介だ。

相対パス

 問題は相対パスだ。わずかな表現のちがいでありながら、まったくことなるパスを指す。これがじつに紛らわしい。

パス 意味
/style.css {base_url}からの相対パス
style.css 現在HTMLが存在するディレクトリからの相対パス
./style.css 現在HTMLが存在するディレクトリからの相対パス

 さらにまとめてみる。

 相対パスには2種類ある。基準となるパスが違う。

基準 URL先頭
ルートディレクトリ({base_url}) /
現在HTMLディレクト なにもつけない or ./

 Linuxファイルシステムに似ている。ルートは/で表現されるから。先頭/相対パスドメイン名を省略できるものだと覚えておけばいい。

 URLが{base_url}/about/だとしたら、以下のようになる。

相対パス 参照パス
/style.css {base_url}/style.css
style.css {base_url}/about/style.css
./style.css {base_url}/about/style.css

 現在HTMLが存在するディレクトリを基準にして、その親をたどっていくパス指定もできる。

パス 意味
../style.css 現在HTMLが存在するディレクトリからの相対パス(親ディレクトリ)
../../style.css 現在HTMLが存在するディレクトリからの相対パス(2親ディレクトリ)

 たどる親の数だけ../を足していけばいい。

 区別するために名前をつける。

名前 URLが{base_url}/about/時のパス
ルート相対パス /style.css {base_url}/style.css
ファイル相対パス style.css
./style.css
../style.css
{base_url}/about/style.css
{base_url}/about/style.css
{base_url}/style.css

ファイル相対パス

<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="./css/style.css">

 パスの先頭になにもつけないか./をつけると、現在ファイルがあるディレクトリからの相対パスになる。

 このとき、URLが{base_url}/のように末尾に/がつくとCSSが参照できなくなってしまう。なぜならつぎのようなパスになるからだ。

URL 参照パス
{base_url}/about {base_url}/css/style.css
{base_url}/about/ {base_url}/about/css/style.css

 zolastatic/css/style.cssにファイルを作ったとする。このときzola buildするとpublic/配下には/css/style.cssに出力される。URLで参照するなら{base_url}/css/style.cssだ。

項目
作成パス $SITE_ROOT/static/css/style.css
出力パス $SITE_ROOT/public/css/style.css
URL {base_url}/css/style.css

 実際にCSSが存在するパスは{base_url}/css/style.cssなので以下のようになる。

URL 参照パス 存在是非
{base_url}/about {base_url}/css/style.css
{base_url}/about/ {base_url}/about/css/style.css

 URL末尾に/があるかないか。それだけで参照できなくなってしまうバグとなる。もうそれだけでも大問題なのだが、さらに問題がある。

 CSS参照するHTMLはテンプレートで共有することが多いだろう。とくにpage.htmlは異なるセクションの単一ページすべてに流用される。このとき罠になるのは、その単一ページが存在するパス階層がちがうという点だ。たとえば/about, /about/, /blob/2021-08-01/のように。

 それぞれのページにおいて存在する階層がちがうなら、相対パスも深さのちがいを考慮せねばならない。たとえばabout, about/の2階層分なら以下のようになる。

<!-- {base_url}/about -->
<link rel="stylesheet" href="css/style.css">
<!-- {base_url}/about/ -->
<link rel="stylesheet" href="../css/style.css">

 ほかにもセクションがあれば/blob/2021-08-01/のようになる。さらに、セクションの中に子セクションを作ることもできる。無限に階層を深くできるのだ。では、一体いくつの階層パターンを用意すればいいのか。漏れや抜けだってありうる。

 また、たくさんURLパターンを用意したところで、実際に存在しているのはひとつだけだ。それ以外のURLは参照エラーになってしまう。ブラウザの開発ツールでコンソールを開いたらエラーになっているのがわかる。

 ファイル相対パスで指定するのは非現実的だ。

 よって、異なる階層から参照されるときはルート相対パスで指定すべきである。

ルート相対パス

<link rel="stylesheet" href="/css/style.css">

 パスの先頭に/をつけると、ルートからの相対パスになる。

 このときはURLの末尾に/があってもなくても、CSSを参照できる。

 なぜならURLに関わらずパスは必ず{base_url}/css/style.cssになるからだ。

結論

 異なる階層から参照されうるときはルート相対パスを使おう。zolaならテンプレートであるpage.html, section.htmlではルート相対パスを使うべし。もしファイル相対パスにすると、URL末尾に/がついたら参照できなくなってしまう。

 つまるところ、URL指定するときは以下の要点を理解しておけばよい。

パス表記方法 HTML内パス URLが{base_url}/about/時のHTML内パス
絶対パス {base_url}/css/style.css {base_url}/css/style.css
ルート相対パス /css/style.css {base_url}/css/style.css
ファイル相対パス css/style.css
./css/style.css
../css/style.css
{base_url}/about/css/style.css
{base_url}/about/css/style.css
{base_url}/css/style.css

所感

 こういう小さい罠ってすごく腹が立つ。なんなの? こんな些細なことを気にしなきゃいけないの? バカなの? 私がバカなんだろうな。知ってた。

対象環境

$ uname -a
Linux raspberrypi 5.4.83-v7l+ #1379 SMP Mon Dec 14 13:11:54 GMT 2020 armv7l GNU/Linux