捨棄 create-react-app 之餘還架了個 astro blog 昭告天下:動態路由與 /tag

你的部落格現在有個不錯的門面、幾篇內容豐富的文章、還有一個 /archive 頁列出所有你發布過的內容。你感覺現在還可以新增一個畫面 /tag,這個畫面會列出你所有文章用過的標籤,並且當使用者點擊各個標籤後,他可以看到所有「包含標籤X」的文章。

如果沒有 astro 的 dynamic route 功能時,要完成以上內容,首先你要:

  1. 整理出所有文章的標籤,然後在 ./src/pages 下建立一個 tag 資料夾,新增 index.astro 並在此元件內渲染所有的標籤
  2. 接著在 tag 資料夾中,幫每一個標籤都建立一個 [標籤名稱].astro 元件,且在每一個 [標籤名稱].astro 中列出所有「帶有標籤X」的文章

但手動維護「對應每一個標籤的 astro 元件」實在太麻煩了,就算可以寫一個腳本自動整理出「目前有在使用的文章標籤」,你還是得根據整理完的資料手動建立每一個標籤的畫面元件。更別說還得避免檔案名稱打錯的問題(比如到底是標籤 test 還是 Test)。

這些問題 astro 都有想到。要解決這個問題,你需要的東西叫 dynamic route (`3´)!

本文

什麼是 dynamic route

延續上方的標籤情境。要解決這個問題,你只要在 ./src/pages/tag 中新增一個檔案 [tag].astro(注意檔案命名一定要包含 []),並在這個 [tag].astro 元件的 Component Script 區匯出(export)一個名為 getStaticPaths 的功能即可:

---
const ALL_UNIQUE_TAGS = [...] // 這是一個包含「所有部落格文章」的標籤陣列,稍後會解釋這個變數怎麼來

export async function getStaticPaths() {
  return ALL_UNIQUE_TAGS.map((tag) => ({ params: { tag } }));
}
---

注意事項:

  1. getStaticPaths 必定回傳陣列型態的資料,且每一個項目都須包含 params
  2. { params: { tag } } 中的 tag 對應檔案名稱 [] 中的內容;假設今天我們將 ./src/pages/tag 中的檔案命名為 [blog_tag].astro,這裡回傳的物件就要改為 { params: { blog_tag } }

只要做到這兩件事情,你就不再需要煩惱到底用過哪些標籤,astro 會幫你把每一個標籤對應的路由畫面都建立好。

實際應用

(以下講解的程式碼內容請參考 src/pages/tag/[tag].astrotools/post.ts


首先移動到 tools/post.ts 中。先加工老朋友 ALL_SORTED_POSTS(此變數詳細資訊可參考第 20 天)來取得全站文章用過的所有標籤,並執行 .flat() 讓陣列變為一維的 string[]

const ALL_TAGS = ALL_SORTED_POSTS.map((post) => post.frontmatter.tag)
  .flat(2)
  .sort();

接著再透過 new Set() 即可取得不重複的標籤陣列:

export const ALL_UNIQUE_TAGS = [...new Set(ALL_TAGS)];

回到 ./src/pages/tag。我們在 Component Script 部位設定好 getStaticPaths() 後,剩下的工作是「把屬於這個標籤的文章都過濾出來」:

const { tag } = Astro.params;
const posts = ALL_SORTED_POSTS.filter((post) =>
  post.frontmatter.tag.flat().includes(tag || '')
);

執行 const { tag } = Astro.params; 就是在取得「對應這個 [tag].astro」的標籤名稱。比如在路由 /tag/TypeScript 時,tag 為 TypeScript、在 /tag/react 時,tag 就是 react。

取得文章資料後,即可在 Component Template 部位將「屬於標籤X的所有文章」渲染出來:

<Html>
  <ul class="post-container">
    {posts.map((post) => <PostItem post={post} />)}
  </ul>
</Html>

透過 astro 處理 dynamic route 就是如此簡單。

處理 /tag 頁

ALL_UNIQUE_TAGS 中的項目放到畫面上,再設定 href 讓使用者可以進入各個 [tag].astro 頁面即可:

<Html>
  <ul class="tag-container">
    {ALL_UNIQUE_TAGS.map((tag) => (
      <li>
        <a class="tag" href={`/tag/${tag}`}>
          {tag}
        </a>
      </li>
    ))}
  </ul>
</Html>

總結

把 astro 的動態路由就是這麼簡單,甚至有內建的 pagination 功能,基本上就是 getStaticPaths() 的延伸應用。官方範例十分詳細,有需要可直接參考+魔改即可。

最後,感謝你今天的閱讀 (・ω・)

參考