title: "修復棕地 SPA 中細微的快取不一致:一個務實的解法"
published: true
description: "我們如何在 DEV 的部署過程中,消除那些細微的樣式表快取異常,而無需大規模重寫。"
tags:
公開開發(building in public)通常意味著談論閃亮的新功能,但在成熟的正式環境應用程式中,最關鍵的工程工作通常是 棕地問題解決(brownfield problem solving)——在多年累積的程式邏輯和/或正式系統決策之上持續迭代。
在 DEV(由開源 Forem 程式碼庫驅動)上,我們有一套混合式架構,結合了 Rails 伺服器端渲染、Fastly 邊緣快取,以及輕量級的前端導覽(透過 InstantClick)。這套架構可提供低於 100ms 的頁面切換,但局部頁面替換搭配激進的邊緣快取,也帶來了微妙的部署挑戰。
我們推出了一個修正 PR #23789,用來協助解決 Web 導覽中本質上就很脆弱的快取不一致問題。這個問題幾乎從 DEV 一開始就存在,我們過去也曾為此做過幾次不太穩定的修補,但我認為這次確實是朝正確方向邁出了一步——儘管稱不上是完全完美的修復。
當我們部署新的 CSS 更新時,Rails 會產生新的資產摘要雜湊值(例如,views-v2.css 取代 views-v1.css)。
<head> 中載入最新的 v2 樣式表。#page-content 容器替換進現有的 DOM。v1 樣式表。為了避免頁面以過時的 CSS 呈現,我們先前加入了前端邏輯,去檢查進入頁面預期的樣式表路徑,並動態替換 DOM 中的 <link rel="stylesheet"> 元素。
實際上,這種做法很脆弱:
v2)導覽到一個較舊、且被邊緣快取的文章(v1)時,前端腳本會比較 currentHref (v2) !== expectedHref (v1)。v1 樣式表。v1 的資產檔案在部署後已被清除,瀏覽器就會因為 404 而失敗;如果載入成功,新 UI 元件又會因為更新後的 CSS 在會話中途被移除而突然壞掉。<head> 與 <body> 之間非同步替換多個樣式表標籤,會引入 CSS 層疊順序的競態條件,以及無樣式內容閃爍(FOUC)。我們沒有把這件事當成前端 DOM 變異問題來處理,而是意識到它本質上是一個 快取分區(cache partitioning)問題。
多年來,Forem 一直悄悄地在背景 AJAX 請求中傳遞一個內部查詢參數——?i=i——讓 Rails 知道要回傳輕量級的部分版型,而不是完整的 HTML 文件外框。
在 PR #23789 中,我們重新利用了這個參數:
合併樣式指紋: 在初次完整頁面載入時,Rails 會根據核心樣式表(minimal、views 和 crayons)的合併摘要計算出一個可重現的 10 字元雜湊,並將其以 data-style-fingerprint 的形式放在 <body> 上:
<body ... data-style-fingerprint="cc9ed033eb">
為站內導覽加上參數: 當 InstantClick 預載或取得某個連結時,它會讀取目前工作階段的指紋,並將其附加到內部網址上:
https://dev.to/user/post?i=cc9ed033eb
(如果 data-style-fingerprint 不存在——例如在部署切換期間開著的分頁——就會優雅地退回到 ?i=i。)
在 Fastly 上自然進行快取分區: Fastly 的邊緣設定已經將 i 列入安全參數清單。由於查詢參數是快取鍵的一部分:
v2 工作階段的使用者請求 /post?i=v2_fingerprint 時,Fastly 會檢查是否有快取的 v2 片段。v2 樣式編譯出的新鮮片段。v1 快取片段會被直接略過,並在之後自然過期。刪除 DOM 替換邏輯: 有了快取分區之後,傳入的局部片段就能保證與目前 DOM 的樣式表版本一致。我們完全刪除了前端樣式表替換腳本。瀏覽器中作用中的 <link> 標籤在整個使用者工作階段中都保持靜態不變。
?i=... 內部參數,就讓我們以零額外基礎設施成本取得了精準的快取鍵。編碼愉快!
原文出處:https://dev.to/devteam/fixing-delicate-cache-mismatches-in-a-brownfield-spa-a-pragmatic-solution-dk9