• 作成:

サイトの記事一覧を年度別に分割しました

この記事を書き始めた現状はこのサイトの全てのentry記事はトップページに全部並んでいます。

500記事ぐらいあるのにサイズやパフォーマンスは大丈夫かというと意外と大丈夫です。サイズ的にはテキストだけなので520KBしかありません。パフォーマンスもシンプルなCSSだけを使って特別なルールは設定していないのでそんなに重くありません。

しかし複数の理由からentryを分割したいと思うようになりました。

だいたい年単位ぐらいで分割したい。

Google検索が無駄にトップページを表示することが多い

トップページには各記事のタイトルと日付とteaserとして記事本文の冒頭の切り取りを載せています。これがGoogle検索はコンテンツだと誤認して複数の検索ワードを複数の記事のteaserに当てはめてトップサイトを持ってくることがかなりあります。

nocontentのクラスを割り当てると良いらしいと目にしたので行ってたのですが、無駄だったようですね。

記事一覧がある以上この問題からは完全に逃れることは出来ませんが、コンテンツを少なくすれば問題を多少軽減することが可能でしょう。

内部リンク扱いされなくてインデックス未登録になっている記事が多数ある

クロール済み - インデックス未登録検出 - インデックス未登録になっている記事がそこそこあります。その記事へのリダイレクトの短縮URLは検索結果に出てきたりするので、コンテンツが低品質とかの問題ではないと思うのですが。

内部リンクがない扱いされてるのでsitemapだけでは不十分で、十分な内部リンクを作る必要があるのかなと感じました。

現状でもトップページからリンクは貼ってるのですが、それが多すぎ扱いになって内部リンク扱いとして認識されてないのかもしれません。

これも数年前は問題なかったので年単位で分割するぐらいのサイズにすれば解決する可能性がありそうです。

実現したいこと

現在トップサイトに全部あるentryへのカードを2016, 2017…と言った子のパスのページに分割する。メンテナンスするのが面倒なため、自動的に年ディレクトリの増減に対応する。

どうしても難しかったらentryパスでページネーションします。

実装方法

Hakyllに沿う方法だとダメでした

ちょっと調べてみて、 Hakyll.Web.Paginate が解決するのに当てはまる問題なのではないかと見当を付けました。

これはどちらかというと記事本体のページネーションのように見えますが、一覧ページの生成にも使えそうに見えます。

Hakyll.Web.Template.List は生成時の時の補助ですかね。これ単体では作れなさそうです。

とりあえずPaginateを作らないと話にならないので、 buildPaginateWithを使って生成しましょう。生成しようと思って考えましたが、これまず複数のidentityを作らないとだめなので出番は無いかも?

とりあえず2022年のだけ固定で作れるようにしてだんだん欲求を上げていくスタイルで書いていくことにしました。

とりあえず年ごとに分割する関数を書いて2022年のだけに絞るようにしてみたのですが、

    let groupByYear entryList = do
          let getDate entry = getMetadataField' (itemIdentifier entry) "date"
          dateList <- mapM getDate entryList
          let getYear = L.takeWhile (/= '/')
          let yearList = getYear <$> dateList
          -- 記事に対応する年のリストを作ってMapで分類。
          pure $ Map.fromDescListWith (<>) $ zip yearList (L.singleton <$> entryList)

このサイトのページ、 Markdownの上部のメタデータフィールドはタイトルしか書いてないからdateは取得できないですね。というわけでentryContextの影響下の中でdateを取得しないといけないのか、メタデータではない単なるフィールドを取得しなければいけないのか、ドキュメントを見てもフィールドを設定する方法や、テンプレートから取得する方法は多く書かれていますが、 Haskellコードから取得する方法はいまいちよくわかりません。ソースを見てみましょう。うーん設定する方式とか既にメタデータにあるものを取る方法は書いてるんですが、コンテキストにあるものを取得する方法がわからない…

loadAllするファイルを年で分割するしかないのかなあ、なんかちゃんとHakyllのフレームワークに乗れた気がしてなくてやりたくなかったのですが、命名規則がガタガタというわけでもなくてある程度統制取れてるので、もうそれで良いのではとなっています。外からファイル名で取っておけば複数ページ作るのも自由自在ですし。

細かいメタデータとかで色々分類する、それこそタグとか。と言った話ならHakyllの道に沿う意義は大いにあるのですが、特に無いことに気がついてしまいました。

createを複数mapM_で繰り返しても、一つしかページが発行されない。

ではcreateの引数名を変えて、そうするとgetResourceBodyで取得できなくなるので、 loadを使いましたが、

  [ERROR] Hakyll.Core.Compiler.Require.load: entry-index.html (snapshot _final) was not found in the cache, the cache might be corrupted or the item you are referring to might not exist

のようなエラーが出てしまう。単にファイル取得したいだけなのにunsafeの術式を使わなくてはいけないようだ。

外部のIOでごり押しして解決

unsafeCompiler (Item (fromFilePath $ "entry-index-of-" <> year <> ".html") <$> readFile "entry-index.html")

とかいうゴミみたいな方法で一応解決してしまった。なんか噛み合って無くて嫌ですね…

よく考えてみると外部のIOで読み込めば良いだけだなと気がついたので書き換え。

main :: IO ()
main =
  pre >>=
  hakyllRun

-- | `Rules`は内部的には`IO`をベースに持ちますが、
-- unsafe系以外で持ち上げる方法が見つからないので、
-- 別コンテキストで処理します。
pre :: IO (String, [String])
pre = do
  entryIndex <- readFile "entry-index.html"
  years <- yearInEntry
  return (entryIndex, years)

純粋な文字列があればmakeItemするだけで良い。

makeItem entryIndex

やった方が良さそうだがやってないこと

年度別の記事一覧に記事数を併記する。 Item作ってる頃にloadとか使えば出来そうですが、そもそも本当に必要なのか疑問になってきてとりあえずやってません。