跳至主要內容

開發紀錄

把 Vue SPA 拆成 Astro 路由後,責任如何重新分配

沿著作品頁與筆記頁,追查路由、layout、內容資料和瀏覽器腳本各自決定什麼,避免移除 SPA 後,又用一個巨型元件接回所有責任。

林予晴
發布
  • Astro
  • 架構重構
  • 責任分工
  • 漸進增強

從 Vue SPA 換成 Astro,原本由應用程式處理的事情仍然存在:網址要找到內容,頁面要有標題和導覽,作品要排序,控制也要回應操作。拿掉 Router 和全域狀態,只是移除一種實作方式,還沒有回答這些責任要交給誰。

本站選擇把它們拆開。網址由原生頁面承接,共用外殼由 layout 組合,內容選擇有自己的資料規則,瀏覽器腳本則接手局部操作。我更在意的不是目錄看起來整齊,而是改一條規則時,能知道它會影響哪些輸出。

重構前的盤點先回答哪些需求要保留;這篇往下一步走,說明保留之後的責任如何落地。

先把初始頁面與進頁後的行為分開

作品標題、介紹、圖片與外連,應該在讀者取得的 HTML 裡;類別切換、瀑布流排版與動畫,則可能需要瀏覽器執行腳本。它們出現在同一張畫面上,不表示必須由同一份執行期狀態一起管理。

本站的分工可以整理成下面這張表。這是目前實作的責任摘要,不是 Astro 規定所有專案都要採用的架構。

層次 決定的事情 不宜順便承接的事情
路由頁面 這個網址使用哪個語言、版型和內容;動態路由要生成哪些頁 每個卡片的呈現細節與所有控制的狀態
共用 layout HTML 外殼、metadata、導覽與頁尾,或文章共有結構 所有頁面的資料選擇與業務規則
內容與資料規則 作品順序、文章身份、公開資格與查詢結果 DOM 事件、捲動位置和焦點
內容元件 把傳入的語言、資料與文案組成可讀內容 自行改寫公開資格或整站導覽規則
瀏覽器腳本 回應局部操作,處理排版更新與相關資源生命週期 等進頁後才補上主要正文或重建整站路由

Astro 官方把 layout 描述為提供可重用 UI 結構的 Astro 元件,並不把它視為另一種特殊應用程式容器。[1] 這個區別讓我不必在新 layout 裡重新建立一個管理所有頁面的 App。

用作品頁追一次責任的流向

本站的繁中作品網址是 /about/work。路由頁面選擇繁中語言,把作品內容元件放進共用外殼;作品元件再讀取已排序的專案資料,套用既有的排除條件,輸出卡片。

資料層的順序與頁面的展示範圍要分開看。排序後的資料還有未公開展示的項目,不能因為它們都在同一份資料裡,就在路由頁面全部攤開。路由知道自己是作品頁,並不因此取得重新決定公開內容的責任。

卡片也有自己的責任:依語言選取標題、帶出日期與圖片,只有資料真的提供原始碼入口時才顯示對應連結。這些呈現條件不需要變成整站 store。

到這裡,讀者已經能在 HTML 裡看到作品。後續腳本再處理開發與設計兩個區塊的切換,以及卡片排版;它不必重新取得作品資料才能讓第一張卡片出現。

筆記頁多了內容規則,路由仍然不要自己發明規則

作品頁是固定入口,筆記文章則需要根據內容生成網址。本站正式文章路由從 getPublishedNotes() 取得公開集合,再依語言選取文章、用 slug 建立路徑,以 stableId 找到同篇翻譯。

這裡有一個刻意保留的邊界:路由決定哪些查詢結果要生成頁面,但「什麼叫可公開」由共用內容規則判斷,不在每個路由重寫一份 draft 和日期條件。

拿到文章之後,文章 layout 負責渲染正文、組合作者與日期、從正文 heading 產生目錄,並把文章的 metadata 和實際翻譯入口交給網站 layout。網站 layout 再輸出 lang、canonical、hreflang 與共用導覽。

因此,文章是否公開、文章如何閱讀、頁面如何標示自己的網址,是三個相連但不同的問題。它們需要一致的資料,卻不需要全部塞進同一個元件。

如果想追公開資格的完整契約,可以接著讀共用發布查詢;這篇只把它放回整個責任流裡。

瀏覽器腳本接管的是操作,不是頁面的身份

作品切換目前使用 #development 與 #design。原生連結先保留可辨識的目的地,腳本再讀取 hash、調整可見區塊和目前選取狀態。語言與文章身份則仍由網址和建置資料決定,不因為切了一個類別就改動。

這種安排也讓無 JavaScript 的狀態有具體意義:作品兩個區塊都已在 HTML 裡,讀者仍能閱讀和使用原生連結,只是沒有腳本提供的切換與排版效果。不能把「無腳本可讀」說成「每個互動在無腳本時完全相同」。

Astro 官方文件說明,可以在元件裡透過 <script> 與 addEventListener 處理操作,不必為了事件綁定引入 UI framework。[2] 不過,使用原生事件並不會自動處理清理與恢復。本站作品腳本仍要管理待執行的動畫、ResizeObserver 和 listeners;離頁與 BFCache 恢復的路徑,也要各自留下責任。

這篇依據程式碼說明分工,沒有把閱讀到的清理分支冒充成所有瀏覽器生命週期都已實測。

共用外殼不等於共用所有判斷

把每個頁面的差異寫成一長串條件,集中放進 layout,表面上也能減少檔案數。可是新增一篇文章、調整作品展示範圍,甚至換一個標題,都可能需要碰同一份巨型元件。

我會用「這條規則因為什麼事情而改變」判斷放置位置。文章發布資格會因內容契約而變動;卡片外觀會因介面設計而變動;網站 metadata 的輸出則有共用格式。它們的修改原因不同,就不必為了共用兩個字強行放在一起。

共用也不是禁止跨層查詢。本站文章 layout 會查同語言的正式文章作為延伸閱讀。重點是它沿用公開集合,而不是趁著顯示相關內容再制定另一套發布資格。

修改時沿著輸出追,不只看檔案在哪一層

下面是依這份責任分工整理的檢查方式,不是本輪新增的測試結果。

  • 改作品資料或排除條件,讀回卡片內容、數量和排序,不只確認資料檔可匯入。
  • 改文章公開資格,同時核對首頁、列表、文章路由、延伸閱讀和 sitemap,避免某個出口仍使用舊條件。
  • 改 layout 的 metadata,檢查受影響語言的實際 HTML,而不是只確認頁面標題在瀏覽器腳本執行後看起來正確。
  • 改互動腳本,除了操作後的狀態,也確認初始 HTML、無腳本可讀性,以及它建立的動畫與 listeners 要如何停止或恢復。

責任拆開之後,檢查反而不能只停在那個檔案。規則有自己的來源,影響則會沿著資料一路到達頁面;兩邊都看,才知道修改是否接到了真正的使用點。

紀錄範圍

本文依據本站目前的作品路由、網站與文章 layout、作品元件與腳本,以及公開筆記查詢整理。它描述原生 Astro 靜態網站的責任分工,不涵蓋登入、遠端資料操作或資料庫交易,也不主張需要大量共享即時狀態的應用程式都應移除 UI framework。

這份分工不是效能、SEO、所有裝置相容性或正式部署的證明。框架選擇另見Vue 轉 Astro 的紀錄;是否完整保留可見行為,仍要用對應的輸出與操作驗證回答。

Sources

[1] https://docs.astro.build/en/basics/layouts [2] https://docs.astro.build/en/guides/client-side-scripts