前端 system design:從載入到上線的取捨清單

文字解析 · 1 支影片 · 產生於 2026-10-01 00:54

🎧 語音解析📝 筆記✍️ 練習

🎧 語音解析 22m11s · TTS 講解與作者原聲交錯;點章節可跳。
邊聽邊看逐句講稿,點任一句從那裡開始
章節(13)

1. Outline

  1. 起點 · Frontend System Design at Scale (The Senior Dev Playbook)
    用「使用者打開網頁之後會發生什麼」這條軸,把前端 system design 的十三個面向串成一條推理鏈:先把程式碼送到瀏覽器(bundling → micro-frontend → repo 策略 → module federation → CDN → bundle 最佳化),再處理它跑起來之後的事(state 分類 → 即時同步),最後是怎麼安全上線與壞掉時怎麼撐住(CI/feature flag → error boundary/監控),收在「先問限制再提方案」。

2. YouTuber 的思維推導

Frontend System Design at Scale (The Senior Dev Playbook)

Monsterlessons Academy · 7m12s · 字幕 en · vision=on (使用者指定 --vision true;影片本身是投影片型,程式碼與架構圖直接承載論點(lodash import 對照、CDN 延遲圖、state 三分表),不看圖會漏掉作者真正的證據。) · YouTube

預估 vs 實際耗時
階段預估實際
fetch 6s 1s
segment 44s –
shot 23s 3s
analyze 3m45s –
render 5s –

作者的推導有一條清楚的主軸:他不是在教「前端 system design 有哪些題目」,而是在示範「資深工程師怎麼把一個需求推到底」。他先立下前提——這是開放題,面試官買的是思考過程而不是名詞——接著沿著「使用者打開網頁之後會發生什麼」一路往下推:先是檔案怎麼送到瀏覽器(bundling → 切 chunk),再把切割的刀口從檔案提升到團隊(micro-frontend),拆開之後自然要問程式碼放哪(monorepo / multi-repo)、執行期怎麼接回來(module federation)、這些片段從哪裡下載(CDN 與快取)、下載的東西本身夠不夠瘦與執行順序對不對(tree shaking、defer)。檔案進到瀏覽器之後,主題轉向 runtime:資料怎麼分類與管理(三種 state)、資料如果是伺服器主動推來的又該怎麼辦(WebSocket 與斷線重連)。

最後兩層是把這一切送上線並維持住:CI、feature flag 與 A/B testing 決定「怎麼安全地改」,error boundary 與監控決定「壞掉時怎麼撐住並修好」。收尾時他把整條鏈折回起點:既然每個環節都有取捨,那麼正確的第一步不是給答案,而是先問限制。每一段的追問(followup)是這支影片的隱藏結構——他反覆示範資深與中階的差別不在知道名詞,而在知道每個選擇的代價。

推理鏈:每段留給下一段的線索

每一列是一段,箭頭後的字是這段留給下一段的線索。點段落標題跳到該段。

  1. 面試不是背答案,是給思路 → 既然評的是思路而不是答案,那就產生一個新問題:一道沒有標準答案的題目,要從哪裡開始講?
  2. 前端 system design 是開放題 → 清單要從哪一項開始?作者選了使用者打開網頁後第一件真正發生的事——把 JavaScript 檔案載下來。
  3. Bundling:把大檔切成 chunk → chunk 切的是「檔案」這一刀。但如果要切的不是檔案,而是「哪個團隊負責哪一塊」,同一把刀還管用嗎?
  4. Micro-frontend:切團隊也切複雜度 → 既然要讓各團隊互不干擾,下一個立刻要決定的問題是:這些拆開的程式碼到底該放在幾個 repository 裡?
  5. Monorepo vs multi-repo → 程式碼放哪解決了,但拆開的片段在使用者瀏覽器裡是怎麼被重新組回一個畫面的?
  6. Module federation:各自部署 → 那份 remoteEntry.js 是從一個 CDN 網址載下來的——CDN 到底幫我們省下了什麼,值得單獨談一段。
  7. CDN 與快取:距離就是延遲 → CDN 縮短的是傳輸距離,但檔案本身太肥、或載入順序不對的話,使用者還是在等——問題得回到 bundle 的內容本身。
  8. Bundle 最佳化與瀏覽器載入順序 → 檔案終於快速地載進瀏覽器並跑起來了——接下來要處理的是它跑起來之後手上那些資料該怎麼管。
  9. State management:三種 state → server state 的模型假設是「我需要時去拉」。但如果資料是伺服器主動推過來的,這個模型就不夠用了。
  10. 即時同步與斷線後怎麼辦 → 架構、資料與即時性都設計好了,但這一切要能持續進到正式環境才有價值——接下來是程式碼怎麼被安全地送上線。
  11. CI、feature flag 與 A/B 測試 → 程式碼安全地上線了,但正式環境一定會壞——最後要處理的是壞掉的時候怎麼撐住並修好。
  12. 可靠性:不要整頁白畫面 → 技術面到此走完一輪。但一場面試講不完十三項——所以最後要回答的是:面對一道題,你要先挑哪一項來講?
  13. 先問限制,再提方案

3. 逐段說明

Frontend System Design at Scale (The Senior Dev Playbook)

1. 面試不是背答案,是給思路 0:00–0:31

作者開場點出:要通過資深面試、要真的做好資深工作,都必須理解什麼是 system design、怎麼做出可擴展的應用。他觀察到很多人在面試時「只是在回答問題」,對 senior 職位來說這樣不夠。這支影片要教的是怎麼回答 system design 問題,以及該講些什麼。

承上 前情提要裡「這是一支給準備資深前端面試的人看的影片」這個設定:讀者已經知道自己要面對的是 system design 這一關,但不知道這一關實際上在評什麼。

推理作者開場先把評分標準講清楚:他觀察到很多人在面試裡「只是在回答問題」,而這對 senior 職位不夠。因為如果評分標準是「答對」,準備方式就是背清單;他要先推翻這個假設,後面所有內容才有意義——他要教的不是清單,是怎麼講。

AI 補充作者沒有明說「只是在回答問題」和「展現思路」差在哪,但這是整支影片的判準,值得先補上:回答問題是「有沒有這個東西」的是非題(「你會用 CDN 嗎?」「會」),展現思路是「在什麼條件下我選它、代價是什麼」的取捨題。面試官問 system design 通常沒有標準答案可對,他真正在觀察的是你會不會自己界定問題、會不會主動指出方案的缺點。這也解釋了為什麼同一份技術清單,中階講出來像背誦、資深講出來像決策紀錄。

術語:senior developer

system design

系統設計

在給定的業務與技術限制下,決定一個系統由哪些元件組成、如何互動、如何擴展的設計工作。

後端的 system design 談的多半是資料庫分片、快取層、訊息佇列;前端的 system design 談的則是把程式碼送到瀏覽器、在瀏覽器裡組裝與維持狀態的整套流程。兩者共通的是「沒有最佳解,只有在特定限制下的合理解」。面試裡它通常是一道 30–45 分鐘的開放題,例如「設計一個電商首頁」,考的是你怎麼把模糊需求收斂成可實作的架構。常見誤解是把它當成「講愈多技術名詞愈好」,實際上講不出取捨的名詞會扣分。

相關術語: scalable architecture (目標)

出處:第 1 段「面試不是背答案,是給思路」

senior developer

資深工程師

被期待能在不確定的需求下自行界定問題、做出取捨並為後果負責的工程師層級。

在這支影片的語境裡,senior 不是年資標籤而是行為標準:中階能正確實作被指派的方案,資深要能說明為什麼選這個方案、放棄了什麼、什麼情況下這個選擇會壞掉。作者整支影片反覆用「你必須解釋為什麼,而不只是有哪些東西」來界定這條線。

相關術語: system design (被考核於)

出處:第 1 段「面試不是背答案,是給思路」

留給下一段 既然評的是思路而不是答案,那就產生一個新問題:一道沒有標準答案的題目,要從哪裡開始講?

2. 前端 system design 是開放題 0:31–0:54

前端的 system design 沒有單一正確答案。面試官要看的是你怎麼思考:怎麼實作一個功能、一個專案,或怎麼把功能擴展出去。接下來作者要逐項談可擴展前端架構最重要的原則。

承上 承上:既然評的是思路,作者接著回答「一道沒有標準答案的題目要怎麼開始」——他先正式承認這題確實沒有單一答案。

推理因為上一段把評分標準從「答對」換成「展現思考」,所以作者這裡必須先定義題型:前端 system design 是開放題,你要展示的是怎麼實作一個功能、一個專案,或怎麼把既有功能擴展出去。定義完題型,他才有立場宣布接下來要走一份「可擴展前端架構最重要的原則」清單——這份清單不是答案本身,是思考時可以掃描的面向。

AI 補充這裡有個容易被忽略的轉折:作者說沒有單一答案,卻馬上開始列清單,看起來矛盾。實際上清單的角色是「檢查面向」而不是「標準答案」——面試時你不會把十三項全講完,而是用它掃描哪幾項跟眼前這題的限制有關。值得注意他排清單的順序不是隨機的,而是跟著使用者的實際體驗走:先是程式碼怎麼送到瀏覽器,再來是送到之後怎麼跑、怎麼維持。用這個順序記,比按技術分類記更不容易漏。

術語:scalable architecture

scalable architecture

可擴展架構

在使用者數、功能數或團隊人數成長時,不需要打掉重練就能繼續加東西的架構設計。

前端的「擴展」有三個互相牽動的維度,這支影片三個都碰到了:載入量的擴展(bundle 愈長愈大怎麼辦)、團隊的擴展(十個人和一百個人改同一份程式碼)、以及功能的擴展(加新功能不會弄壞舊功能)。多數人只想到第一種,但作者接下來花最多時間談的其實是第二種。

相關術語: system design (產出)

出處:第 2 段「前端 system design 是開放題」

留給下一段 清單要從哪一項開始?作者選了使用者打開網頁後第一件真正發生的事——把 JavaScript 檔案載下來。

3. Bundling:把大檔切成 chunk 0:54–1:21

第一個一定要提的是 bundling。把整份前端 JavaScript 打包成單一檔案並不快,5 MB 以上的檔案載入太久。正解是 lazy loading:把巨大的 bundle 切成獨立 chunk,每個頁面只載入自己需要的那塊。

投影片直接給出 React.lazy + 動態 import 的兩行寫法,說明「切 chunk」在程式碼層面長什麼樣——這是整段抽象論述的具體落點。
1:12 · 投影片直接給出 React.lazy + 動態 import 的兩行寫法,說明「切 chunk」在程式碼層面長什麼樣——這是整段抽象論述的具體落點。
承上 承上:清單的第一項要從使用者第一個碰到的環節開始,也就是瀏覽器把前端 JavaScript 抓下來這一步。

推理因為上一段決定要照「使用者實際遇到的順序」走,所以作者從 bundling 起手:把整份前端 JavaScript 打包成一個檔案並不快,5 MB 以上載入太久。他接著給出對應的解法——lazy loading 加上把大 bundle 切成獨立 chunk,每頁只載自己要的那塊。這一段建立了整支影片反覆出現的句型:先指出一個會隨規模惡化的問題,再給一個把「一整塊」拆成「按需要的小塊」的解法。

AI 補充投影片給的是 React.lazy 加動態 import 的兩行寫法,這裡值得補上它為什麼有效:`import('./Dashboard')` 這種動態語法在 build 階段就是給打包工具的切割記號,Vite 或 webpack 看到它就把 Dashboard 及其相依單獨輸出成一個檔案,執行期真的走到那一行才發請求。也就是說切割發生在建置期,決定要不要載發生在執行期,兩件事的分工要講清楚。另外作者的「5 MB」不必當成硬門檻——真正該看的是使用者在目標網路環境下等多久,行動網路上 1 MB 的 JavaScript 就足以讓首屏明顯變慢,因為 JS 除了下載還要解析和執行。實務上先切的通常是路由層級(每個頁面一個 chunk),再視情況切到元件層級(例如很少開的圖表或編輯器)。

術語:code splitting

bundling

打包

把分散的原始碼模組與相依函式庫合併成瀏覽器能載入的少數幾個檔案的建置步驟。

早期瀏覽器沒有模組系統,打包是必要之惡;現在瀏覽器支援 ES module,但打包仍在做三件事:減少請求數、做壓縮與最佳化、以及處理 TypeScript/JSX 這類需要轉譯的語法。打包工具的預設行為是「全部塞進一個檔案」,所以檔案膨脹不是意外而是預設值——這正是本段問題的來源。

相關術語: code splitting (解法)

出處:第 3 段「Bundling:把大檔切成 chunk」

code splitting

程式碼分割

在建置期把一個大 bundle 拆成多個可獨立載入的檔案,讓每個頁面只下載自己需要的部分。

分割的粒度是個取捨:切太粗省不到多少,切太細會產生大量小請求並讓共用相依重複出現在多個 chunk 裡。打包工具會自動把多個 chunk 共用的模組抽成 vendor 或 common chunk 來緩解這件事。實務上最穩的起手式是按路由切,因為使用者一次只會在一個頁面上。

相關術語: bundling (修正)、chunk (產出)

出處:第 3 段「Bundling:把大檔切成 chunk」

lazy loading

延遲載入

把某塊程式碼或資源的下載推遲到真正需要用到的那一刻才發生。

在 React 裡是 `React.lazy(() => import('./X'))` 搭配 `<Suspense>` 提供載入中的畫面;Vue 則是把元件寫成回傳 import() 的函式。它與 code splitting 是一體兩面:分割決定「檔案怎麼切」,延遲載入決定「什麼時候去拿」。常見的進階做法是預測性預載——例如滑鼠移到連結上就先偷偷載入該頁的 chunk,讓使用者點下去時已經到位。

相關術語: code splitting (執行期對應)

出處:第 3 段「Bundling:把大檔切成 chunk」

chunk

程式碼區塊

code splitting 之後產生的一個可獨立下載的 JavaScript 檔案。

chunk 的檔名通常帶內容雜湊(例如 `Dashboard.a3f9c1.js`),這樣內容沒變時瀏覽器與 CDN 可以一直沿用快取,內容一變檔名就變、快取自然失效。這個「用檔名做快取失效」的手法,之後談快取時會再出現。

相關術語: code splitting (產物)

出處:第 3 段「Bundling:把大檔切成 chunk」

留給下一段 chunk 切的是「檔案」這一刀。但如果要切的不是檔案,而是「哪個團隊負責哪一塊」,同一把刀還管用嗎?

4. Micro-frontend:切團隊也切複雜度 1:21–1:56

第二點是 micro-frontend:把前端應用拆成獨立部分,通常放進不同 repository,由不同團隊各自開發,彼此不互相干擾。代價是把這些部分接回去的複雜度變高。作者強調這只在專案夠大時才划算,幾個頁面的簡單應用不該這樣做。

架構圖把 Shell App (host) 與 Search / Recommendations / Cart 三個團隊區塊畫出來,一眼看出「拆的是團隊邊界」而不是技術層級。
1:40 · 架構圖把 Shell App (host) 與 Search / Recommendations / Cart 三個團隊區塊畫出來,一眼看出「拆的是團隊邊界」而不是技術層級。
承上 承上:上一段把切割的刀口停在「檔案」層級,這一段把同一把刀往上抬到「團隊」層級,看它會不會斷。

推理因為 chunk 只解決載入量、解決不了「二十個人改同一份程式碼會互相踩腳」,所以作者接著提 micro-frontend:把前端應用拆成獨立部分,通常各自放進不同 repository,由不同團隊各自開發、互不干擾。他緊接著自己踩煞車——這樣做會提高把各部分接回去的複雜度,只有專案夠大才划算,幾個頁面的應用這樣做沒有意義。這個「先給好處再自己給代價」的動作,就是他第 1 段講的資深表現的示範。

AI 補充投影片的架構圖把重點畫得很直接:一個 Shell App (host) 裡面放著 Search(A 團隊)、Recommendations(B 團隊)、Cart(C 團隊)。注意框線切的是團隊,不是技術層級——這是 micro-frontend 最容易被誤解的地方,它是一個組織問題的技術解法,不是效能最佳化手段。作者說「複雜度上升」但沒展開,實際的代價相當具體:共用的 React 版本必須協調(否則同一頁載入兩份 React),跨片段的路由與登入狀態要有共同約定,設計系統要抽成共用套件否則畫面會不一致,端對端測試要跨多個部署才跑得起來。判斷門檻可以用一個簡單問題代替「專案夠不夠大」:你們是否已經因為多個團隊搶同一條發布流程而互相等待?沒有這個痛,micro-frontend 買來的就只有成本。

micro-frontend

微前端

把單一前端應用拆成數個可由不同團隊獨立開發、部署的片段,再於執行期組合成一個畫面的架構風格。

概念沿用自後端的 microservice,動機也一樣是組織擴展而非效能。組合的方式有好幾種:build 期整合(各片段發成 npm 套件)、伺服器端組合(由後端拼出 HTML)、以及執行期在瀏覽器組合(作者後面提的 module federation 屬於這種)。愈晚組合,團隊獨立性愈高,執行期的協調成本也愈高。反面案例很常見:三人團隊導入 micro-frontend,結果每加一個功能都要改三個 repo。

相關術語: shell app (由…承載)、code splitting (刀口更上層)

出處:第 4 段「Micro-frontend:切團隊也切複雜度」

shell app

殼層應用 / host

micro-frontend 架構裡負責載入、擺放並協調各獨立片段的主應用。

shell 通常只保留三件事:路由、全域版面(header/sidebar)、以及登入狀態等跨片段共用的東西。它刻意保持很薄,因為 shell 一改就要重新部署,會抵銷各團隊獨立部署的好處。投影片裡 Shell App (host) 外框包住三個團隊區塊,畫的就是這個關係。

相關術語: micro-frontend (組成部分)

出處:第 4 段「Micro-frontend:切團隊也切複雜度」

留給下一段 既然要讓各團隊互不干擾,下一個立刻要決定的問題是:這些拆開的程式碼到底該放在幾個 repository 裡?

5. Monorepo vs multi-repo 1:56–2:32

承接 micro-frontend 的追問:把前端拆到不同 repository 就是 multi-repo;把所有模組放在同一個 repository 就是 monorepo,可以用 Nx 之類的工具管理。作者的重點不是名詞本身,而是資深工程師必須解釋「為什麼」以及兩者的差異。

左右對照表把 monorepo 的目錄結構與 multi-repo 的 versioning hell 並排,還附上「改 shared-ui 要等發版、幾天;monorepo 一個 PR 搞定」的實際成本註解。
2:12 · 左右對照表把 monorepo 的目錄結構與 multi-repo 的 versioning hell 並排,還附上「改 shared-ui 要等發版、幾天;monorepo 一個 PR 搞定」的實際成本註解。
承上 承上:上一段把應用拆給了不同團隊,緊接著要決定的就是這些程式碼放在幾個 repository。

推理因為拆開之後 repository 的擺法變成一個真的要選的問題,所以作者把兩個選項並排:拆到不同 repository 是 multi-repo,全部模組放在同一個 repository 是 monorepo,可以用 Nx 這類工具管理。然後他把這一段的重點從技術拉回第 1 段的判準——資深工程師必須解釋為什麼以及兩者差在哪,而不是只報出這兩個名詞存在。

AI 補充投影片的對照表比口述多給了關鍵的一筆:multi-repo 那一欄標了 versioning hell,並附上實際流程——app-a 要修 shared-ui 的 bug,得開 PR、等發版、升版號,要花好幾天;monorepo 則是一個 PR 就解決。這句註解才是真正的取捨核心:multi-repo 換來的是團隊自主,付出的是跨套件改動的延遲;monorepo 換來的是一次改完,付出的是共用一條 CI、共用工具鏈,以及需要 Nx/Turborepo 這類工具做增量建置,否則改一行就全部重跑。要注意 monorepo 和 micro-frontend(見第 4 段)是兩個獨立的軸,可以自由組合:monorepo 裡放多個可獨立部署的片段是很常見的組合,同一份程式碼庫、各自的發布流程。作者這裡口語上把 monorepo 說成了「monolith versus multi-repo」,看投影片就知道他要對比的是 monorepo。

monorepo

單一儲存庫

把多個專案或模組放在同一個版本控制儲存庫裡一起管理的做法。

Google、Meta 都用超大型 monorepo。它的好處是跨套件的改動可以在一個 commit 裡完成、相依版本天然一致、重構工具能一次看到所有呼叫端。代價是 repo 會變大、CI 需要判斷「這次只有哪些套件受影響」才不會每次全跑,這正是 Nx 與 Turborepo 存在的理由。它不等於 monolith——monolith 指的是部署成一整塊,monorepo 只講程式碼放哪,monorepo 裡完全可以有多個獨立部署的產物。

相關術語: multi-repo (對照組)、Nx (管理工具)

出處:第 5 段「Monorepo vs multi-repo」

multi-repo

多儲存庫

讓每個模組或應用各自擁有一個獨立儲存庫的做法。

每個 repo 有自己的權限、CI 與發布節奏,團隊自主性最高,也最貼近 micro-frontend(見第 4 段)的組織動機。主要痛點是共用程式碼必須走「發套件—升版號—各專案更新」的流程,跨 repo 的改動因此變慢,而且很容易出現不同專案卡在不同版本的共用套件上。

相關術語: monorepo (對照組)

出處:第 5 段「Monorepo vs multi-repo」

Nx

Nx 建置工具

用相依圖判斷哪些專案真的受這次改動影響、只重建與重測那些專案的 monorepo 工具。

同類工具還有 Turborepo(投影片上與 Nx 並列)、Bazel、Lerna。它們的核心價值是把 monorepo 最大的成本——「改一行就全部重跑」——變成增量作業,靠的是相依圖加上建置結果快取。沒有這類工具的 monorepo 在專案變多之後,CI 時間會先撐不住。

相關術語: monorepo (使可行)

出處:第 5 段「Monorepo vs multi-repo」

留給下一段 程式碼放哪解決了,但拆開的片段在使用者瀏覽器裡是怎麼被重新組回一個畫面的?

6. Module federation:各自部署 2:32–2:55

第二個追問是 module federation——把前端切成不同片段、之後在單一專案裡載入的具體機制。關鍵好處是 A 團隊可以部署自己那塊的新版本,主應用會直接吃到新版,不需要所有片段一起重新部署。

webpack config 顯示 host 從 Team B 的 CDN URL 載入 remoteEntry.js,這行 URL 就是「對方獨立部署、我這邊不用重 build」的技術原因。
2:48 · webpack config 顯示 host 從 Team B 的 CDN URL 載入 remoteEntry.js,這行 URL 就是「對方獨立部署、我這邊不用重 build」的技術原因。
承上 承上:上一段留下「拆開的片段在瀏覽器裡怎麼組回一個畫面」,這一段給出具體機制。

推理因為 repository 的擺法決定不了執行期怎麼合體,所以作者把 module federation 當成第二個追問端出來:它正是把前端切成不同片段、之後在單一專案裡載入的方式。他把價值濃縮成一句——A 團隊可以部署自己那塊的新版本,主應用會直接吃到,不必所有片段一起重新部署。這句話正好兌現了第 4 段承諾的「互不干擾」:如果每次改動都要一起發版,拆開就沒有意義。

AI 補充投影片的 webpack 設定把機制講得比口述清楚:host 的 `remotes` 裡寫著 `cart: 'cart@https://cdn.team-b.com/remoteEntry.js'`,接著在 React 程式碼裡用 `React.lazy(() => import('cart/CartWidget'))` 使用它。關鍵在那個 URL——host 在建置時並不打包 cart 的程式碼,只記下去哪裡拿;執行期才抓 remoteEntry.js,那是一份 B 團隊產出的清單,說明有哪些模組、實際檔案在哪。所以 B 團隊重新部署、覆蓋掉那個 URL 的內容,host 下一次載入就自動拿到新版,完全不用重 build。注意這裡重複用到了第 3 段的 `React.lazy` 動態 import:同一個語法,先前切的是自家的 chunk,現在跨過網路邊界去拿別人的模組。代價也在同一個地方——那個 URL 掛掉或版本不相容時,host 就開天窗,而且 React 這類共用相依必須在 `shared` 設定裡協調好,否則同一頁會載入兩份。

module federation

模組聯邦

讓一個應用在執行期從另一個獨立部署的應用載入模組的打包器機制。

由 webpack 5 引入,Vite 與 Rspack 也有對應實作。它與傳統 npm 套件的差別在時機:npm 套件在建置期就被固定進 bundle,換版要重新 build 與部署;federation 在執行期解析,對方部署完就生效。這也是它的風險所在——你把一個建置期就能發現的錯誤(版本不合、模組不存在)推遲到了使用者的瀏覽器裡,所以務必搭配 fallback 與版本協商。

相關術語: micro-frontend (執行期實作)、remoteEntry (入口檔案)

出處:第 6 段「Module federation:各自部署」

remoteEntry

遠端入口清單

被載入的一方產出的小型清單檔,宣告它對外開放哪些模組、實際檔案在哪。

host 只認這個 URL,不認對方的原始碼。remoteEntry.js 本身很小,真正的模組檔案是它再去抓的,所以更新一次部署就換掉整份對應關係。因為 host 每次都要讀它才知道最新狀態,這個檔案的快取時間必須設得很短——否則 B 團隊部署了,使用者卻還吃著舊清單。

相關術語: module federation (運作核心)

出處:第 6 段「Module federation:各自部署」

留給下一段 那份 remoteEntry.js 是從一個 CDN 網址載下來的——CDN 到底幫我們省下了什麼,值得單獨談一段。

7. CDN 與快取:距離就是延遲 2:55–3:36

接著是 CDN 與 caching。作者用具體數字說明價值:沒有 CDN 時,東京的使用者要打到美國的 origin server,大約 200 毫秒;配好 CDN 後從東京當地節點、甚至直接從快取拿,只要幾十毫秒。追問則是資產要快取多久、什麼時候要讓快取失效。

兩條路徑的延遲對照圖(Tokyo → Virginia ~200ms vs Tokyo → Edge node cache hit ~20ms),把 CDN 的收益量化成可以在面試裡直接說出口的數字。
3:27 · 兩條路徑的延遲對照圖(Tokyo → Virginia ~200ms vs Tokyo → Edge node cache hit ~20ms),把 CDN 的收益量化成可以在面試裡直接說出口的數字。
承上 承上:上一段的 remoteEntry.js 掛在一個 CDN 網址上,這一段就來回答 CDN 究竟省下了什麼。

推理因為前面幾段產出的所有檔案(chunk、remoteEntry、各片段的程式碼)都得跨網路送到使用者手上,所以作者把 CDN 與快取拉成獨立一項,並且用數字說服:沒有 CDN 時東京的使用者要打到美國的 origin server,大約 200 毫秒;配好 CDN 之後從東京當地節點、甚至直接命中快取,就只要很短的時間。接著他照慣例給出追問——資產要快取多久、什麼時候要讓它失效。

AI 補充投影片把兩條路徑並排畫出來,數字是 Tokyo → Virginia 約 200ms、Tokyo → Edge node 命中快取約 20ms。這裡值得補一個作者跳過的因果:延遲的主因不是伺服器慢,而是光速加上 TCP/TLS 的來回次數——距離愈遠,每一次握手都要多花一趟往返,所以縮短物理距離的收益是成倍的,不是線性的。至於他留下的追問,實務上的標準答案是把資產分成兩類:帶內容雜湊的檔名(見第 3 段的 chunk 命名)可以設超長快取,因為內容一變檔名就變;而 index.html 與第 6 段的 remoteEntry.js 這種「指路的檔案」必須設極短快取或不快取,否則使用者會拿著舊地圖去找新檔案。這個組合就是「不可變資產長快取 + 入口檔短快取」,也是為什麼快取失效在前端多半靠檔名而不是靠手動清 CDN。

CDN

內容傳遞網路

把靜態資產複製到全球各地的邊緣節點,讓使用者從地理上最近的節點取得檔案的服務。

除了縮短距離,現代 CDN 還順手做了 TLS 終止、HTTP/2 或 HTTP/3、壓縮、圖片格式轉換與 DDoS 防護。要注意它預設只適合公開的靜態資產;帶個人化內容的回應若被誤快取,會把 A 使用者的資料送給 B,這是實務上最常見也最嚴重的 CDN 事故。

相關術語: edge node (組成單位)、cache invalidation (配套問題)

出處:第 7 段「CDN 與快取:距離就是延遲」

edge node

邊緣節點

CDN 部署在各地機房、實際回應使用者請求的伺服器。

使用者的請求先到最近的邊緣節點;節點手上有這份資產就是 cache hit,直接回;沒有就是 cache miss,回源站拿再順手存起來。所以流量低的冷門資產命中率會偏低,第一個使用者仍然要付出完整的往返成本。

相關術語: CDN (屬於)

出處:第 7 段「CDN 與快取:距離就是延遲」

cache invalidation

快取失效

讓已經被快取的舊版本停止被使用、確保使用者拿到新內容的機制。

前端主流做法是 cache busting:把內容雜湊寫進檔名,新版本自然是新網址,舊快取放著自然過期就好,不必主動清。主動清除(purge)留給無法改名的入口檔。快取失效之所以出名地難,是因為要同時考慮瀏覽器快取、CDN 快取與中間的代理快取三層,任何一層設錯都會讓使用者卡在舊版。

相關術語: CDN (維護動作)、chunk (靠檔名解決)

出處:第 7 段「CDN 與快取:距離就是延遲」

origin server

源站

資產的真正來源伺服器,CDN 在快取沒命中時回頭向它索取。

CDN 的另一個常被忽略的價值是保護源站:命中率高的時候,源站的流量可能只剩百分之幾,成本與故障風險一起下降。投影片裡的 Virginia 就是源站所在地,Tokyo 的使用者在沒有 CDN 時得跨太平洋去找它。

相關術語: CDN (被前置於)

出處:第 7 段「CDN 與快取:距離就是延遲」

見仁見智 3:26
「it may take just 2 milliseconds」
口述說命中快取只要 2 毫秒,但他自己的投影片寫的是約 20ms。2ms 對真實的網路往返(含 TLS 與最後一哩)幾乎不可能達到,即使邊緣節點就在同一座城市;20ms 才是同城命中快取的合理量級。面試時引用這個數字建議說「數百毫秒降到數十毫秒」,不要說 2ms。
依據: 同段投影片(影片 03:27)標示 Tokyo → Edge node → Cache hit ≈ 20ms
留給下一段 CDN 縮短的是傳輸距離,但檔案本身太肥、或載入順序不對的話,使用者還是在等——問題得回到 bundle 的內容本身。

8. Bundle 最佳化與瀏覽器載入順序 3:36–4:21

再來是 bundle 最佳化。tree shaking 能砍掉沒用到的函式,但前提是 import 寫對——某些寫法會把整個函式庫拉進來,這個好處就沒了;要查問題可以用 webpack bundle analyzer。第二點是理解瀏覽器怎麼執行 JS 與 CSS、何時 paint 與 repaint,所以要懂 defer 之類的手段;作者看過很多正式環境的 script 互相擋住彼此。

同一個 debounce 的三種 import 寫法並排(整包 lodash 70KB+ vs lodash/debounce 2KB vs lodash-es),把「import 寫錯 tree shaking 就失效」變成可量測的體積差。
3:52 · 同一個 debounce 的三種 import 寫法並排(整包 lodash 70KB+ vs lodash/debounce 2KB vs lodash-es),把「import 寫錯 tree shaking 就失效」變成可量測的體積差。
四行 script 標籤對照:無屬性會擋住 render、defer 平行下載並在解析後執行、async 立即執行、critical CSS 內嵌——這就是上一句「script 互相擋住」的解法清單。
4:00 · 四行 script 標籤對照:無屬性會擋住 render、defer 平行下載並在解析後執行、async 立即執行、critical CSS 內嵌——這就是上一句「script 互相擋住」的解法清單。
承上 承上:上一段把傳輸距離壓到最短之後,剩下的延遲來自檔案本身的體積與執行順序。

推理因為距離已經不是瓶頸,所以作者把注意力轉回檔案內部,講兩件事。第一是 tree shaking:它能砍掉函式庫裡沒用到的函式,但前提是 import 寫對,某些寫法會把整包拉進來、好處就沒了;要查有沒有中招可以用 webpack bundle analyzer。第二是瀏覽器怎麼執行 JS 與 CSS、何時 paint 與 repaint,所以要懂 defer 之類的手段——他說他在正式環境看過大量 script 互相擋住彼此的案例。

AI 補充兩張投影片把這段變得可操作。第一張把同一個 `debounce` 的三種寫法並排:`import _ from 'lodash'` 會拉進整包(70KB+),`import debounce from 'lodash/debounce'` 只有 2KB,`import { debounce } from 'lodash-es'` 則因為是 ES module 格式而能被正確搖掉。原因作者沒說:tree shaking 靠的是 ES module 的靜態結構分析,CommonJS 的 `require` 在執行期才決定拿什麼,工具無從判斷哪些能刪——所以 lodash(CommonJS)要靠子路徑 import,lodash-es 才能享受具名 import。第二張則列出四種 script 寫法:無屬性會擋住解析與繪製、`defer` 平行下載並在 HTML 解析完後依序執行、`async` 下載完立刻執行(適合彼此無關的第三方腳本)、critical CSS 直接內嵌以免首屏等外部樣式檔。這正是他說的「script 互相擋住」的解法清單。順帶一提:`defer`/`async` 只對外部 script 有效,寫在內嵌 script 上不會生效,這是實務上很常見的誤用。

術語:render blocking

tree shaking

搖樹最佳化

打包時靜態分析出沒有被任何地方用到的匯出,並把它們從產物中移除。

名字來自「搖動樹木讓枯葉掉下來」。它成立的前提有兩個:模組必須是 ES module(靜態的 import/export),而且該模組不能有副作用——所以 package.json 裡的 `sideEffects: false` 標記很重要,少了它打包工具會保守地不敢刪。常見誤解是以為 tree shaking 萬能,實際上只要 import 方式或模組格式不對,它就完全失效,而且失效時沒有任何警告。

相關術語: bundle analyzer (驗證工具)、code splitting (同為瘦身手段)

出處:第 8 段「Bundle 最佳化與瀏覽器載入順序」

bundle analyzer

打包分析工具

把打包產物中每個模組佔多少體積視覺化成方塊圖,用來找出異常肥大的相依。

webpack-bundle-analyzer 是最知名的一款,Vite 對應的是 rollup-plugin-visualizer。典型用法是產生圖之後找面積最大的方塊,通常會抓到三種慣犯:整包被引入的工具庫、被打包進來的多國語系檔(moment 的 locales 是經典案例)、以及重複出現的同一套件不同版本。作者提到它正是要回答「我怎麼知道 tree shaking 有沒有生效」。

相關術語: tree shaking (檢查對象)

出處:第 8 段「Bundle 最佳化與瀏覽器載入順序」

defer

延後執行屬性

script 標籤屬性,讓瀏覽器平行下載腳本、等 HTML 解析完成後再依原順序執行。

與 `async` 的差別在兩點:`defer` 保證執行順序、且一定在 DOM 解析完之後;`async` 不保證順序、下載完就搶著執行,可能打斷解析。有相依關係的腳本用 defer,彼此獨立的第三方(分析、客服元件)用 async。兩者都只對有 src 的外部 script 有效。

相關術語: render blocking (解法)

出處:第 8 段「Bundle 最佳化與瀏覽器載入順序」

render blocking

阻擋繪製

瀏覽器在解析 HTML 時必須停下來下載並執行某個資源,導致畫面遲遲無法繪製的情況。

沒有屬性的 `<script src>` 與 `<head>` 裡的外部 CSS 都會阻擋繪製——CSS 是因為瀏覽器不願意先畫出沒有樣式的內容。標準解法是 script 加 defer/async、首屏必要的 critical CSS 內嵌、其餘樣式非同步載入。作者說的「script 互相擋住彼此」就是多個阻擋型腳本串在一起、時間累加的結果。

相關術語: defer (被…緩解)

出處:第 8 段「Bundle 最佳化與瀏覽器載入順序」

確定錯誤/已過時 3:37
「We obviously have tree shaking on the client」
tree shaking 不發生在 client(瀏覽器),而是在建置期由打包工具做靜態分析後移除未使用的匯出——產物送到瀏覽器時已經是砍過的。這個區分很重要:正因為它是建置期的靜態分析,`require` 或整包 import 這種要到執行期才知道用了什麼的寫法才會讓它失效,也就是作者下一句自己提到的坑。
依據: webpack 官方文件 Tree Shaking 章節將其定義為 bundler 在 build 階段依 ES module 靜態結構移除 dead code
留給下一段 檔案終於快速地載進瀏覽器並跑起來了——接下來要處理的是它跑起來之後手上那些資料該怎麼管。

9. State management:三種 state 4:21–5:06

狀態管理的重點是分清楚三種 state:global app state、server state、client state,以及各自該用什麼工具。從 API 拿來的 server state 用 React Query 這類函式庫;全域 UI state 可以用 Redux;區域 state 用 useState 就夠。作者提醒要挑適合該業務的工具,很多專案只用 React Query、寫最少的程式碼就夠了。

三列對照表把 state 類型、實際例子(auth/theme vs modal open)與建議工具(React Query / Zustand / useState)綁在一起,是這段分類法的完整版。
4:48 · 三列對照表把 state 類型、實際例子(auth/theme vs modal open)與建議工具(React Query / Zustand / useState)綁在一起,是這段分類法的完整版。
承上 承上:程式碼已經快速載入並開始執行,接手的問題是執行期手上那些資料該怎麼管。

推理因為前面所有段落談的都是「把程式碼送到瀏覽器」,到這裡主題正式轉入 runtime,所以作者先給分類再給工具:要分清楚 global app state、server state 與 client state,並知道各自該用什麼。從 API 拿來的 server state 用 React Query 這類函式庫,全域 UI state 用 Redux,區域 state 用 useState 就夠。他隨即補上和第 4 段同一種煞車——要挑適合該業務的工具,很多專案只用 React Query、寫最少的程式碼就夠了。

AI 補充投影片的三列表格把分類講得比口述完整:Server State 是 API 來的資料,用 React Query 或 SWR;Global UI State 是 auth、主題、sidebar 開關,用 Zustand 或 Redux;Local UI State 是 modal 開關、輸入框的值,用 useState。這個分類真正的價值在於指出一個歷史誤區:早年大家把 API 資料也塞進 Redux,於是要自己寫 loading、error、快取、重新抓取、race condition 處理——而這些全都是 server state 特有的問題,因為那份資料的真正來源在別人的伺服器上,你手上的永遠只是一份可能過期的副本。React Query 這類工具存在的意義就是把「副本管理」標準化。反過來說,真正的 UI 狀態(modal 開不開)沒有這些問題,用 useState 就夠,硬塞進全域 store 只會製造樣板程式碼。判斷方式很簡單:這份資料的真相在伺服器上,還是只存在於這個瀏覽器分頁裡?

server state

伺服器狀態

真正的來源在後端、前端手上只有一份可能過期的副本的資料。

它的特徵是非同步取得、可能失敗、會被別人改動、需要快取與重新驗證。React Query、SWR、RTK Query 都在做同一件事:把請求去重、背景重新抓取、失效策略與樂觀更新包成宣告式 API。把 server state 誤當一般狀態管理,是前端最常見的架構債來源。

相關術語: React Query (專用工具)、global UI state (對照組)

出處:第 9 段「State management:三種 state」

global UI state

全域介面狀態

只存在於前端、但多個不相鄰的元件都要讀寫的狀態,例如登入資訊、主題、側欄開關。

它沒有「過期」的問題,因為真相就在瀏覽器裡,所以不需要快取或重新驗證,只需要一個所有元件都拿得到的地方。工具選擇是分量問題:Zustand、Jotai 很輕,Redux Toolkit 較重但生態與除錯工具完整。判斷是否需要它的方法:這個值是不是要傳超過三層 props?不是的話留在區域就好。

相關術語: server state (對照組)、local UI state (作用域更大)

出處:第 9 段「State management:三種 state」

local UI state

區域介面狀態

只有單一元件(或它的少數子元件)需要知道的狀態,例如 modal 是否開啟、輸入框目前的值。

在 React 裡就是 useState 或 useReducer。把它提升到全域幾乎總是錯的:作用域愈大,能改到它的地方就愈多,除錯成本跟著上升。實務原則是狀態盡量放在需要它的最小共同祖先,真的要跨遠距離才往上抬。

相關術語: global UI state (作用域更小)

出處:第 9 段「State management:三種 state」

React Query

React Query(TanStack Query)

專門管理 server state 的函式庫,內建快取、背景重新驗證、請求去重與錯誤重試。

核心模型是 stale-while-revalidate:先把快取裡的舊資料交給畫面,同時在背景重抓,回來了再更新。這正是作者說「很多專案只用它、寫最少的程式碼」的原因——原本要手寫的 loading/error/快取/重抓全部內建。它不處理 UI 狀態,所以通常會和一個輕量的全域 store 搭配使用。

相關術語: server state (處理對象)

出處:第 9 段「State management:三種 state」

見仁見智 4:42
「For global UI state you might want to use Redux」
把 Redux 當成全域 UI 狀態的預設選擇在 2026 年已偏保守。純 Redux 的樣板量大,官方本身也推薦改用 Redux Toolkit;而純 UI 狀態的新專案多半直接選 Zustand 或 Jotai(作者自己的投影片就把 Zustand 排在 Redux 前面)。Redux 仍有優勢的場景是需要嚴謹的 action 紀錄、時間旅行除錯,或團隊已有大量既有 Redux 程式碼。
依據: 同段投影片標示「Use: Zustand / Redux」,Zustand 在前;Redux 官方文件自 2021 起以 Redux Toolkit 為建議寫法
留給下一段 server state 的模型假設是「我需要時去拉」。但如果資料是伺服器主動推過來的,這個模型就不夠用了。

10. 即時同步與斷線後怎麼辦 5:06–5:36

延伸出來的是即時同步、WebSocket 與 Socket.IO,因為現在很多專案有通知、協作共享、即時儀表板。作者示範可以自己延伸的追問:連線斷掉時你怎麼做?重播漏掉的事件,還是整份重新抓?答案取決於專案需求。

投影片把作者口頭提的追問寫成標題為 QUESTIONS 的題目清單,示範這類開放題在面試裡該怎麼被主動提出來。
5:29 · 投影片把作者口頭提的追問寫成標題為 QUESTIONS 的題目清單,示範這類開放題在面試裡該怎麼被主動提出來。
承上 承上:上一段的 server state 假設資料是前端主動去拉的;這一段處理資料由伺服器主動推過來的情況。

推理因為「去拉」的模型撐不住通知、協作共享與即時儀表板這些現在很常見的需求,所以作者把即時同步、WebSocket 與 Socket.IO 拉出來講。他接著做了一個很值得學的動作:不是等面試官問,而是示範你可以自己延伸的追問——連線斷掉時你怎麼做,重播漏掉的事件還是整份重新抓?然後回到他的標準答案形狀:看專案需求。

AI 補充投影片直接把這兩個追問寫成一張 QUESTIONS 清單,等於在告訴你:這是可以主動端上桌的問題。作者沒說的是這兩條路各自的代價,而這才是面試裡真正能得分的部分。重播(replay)需要伺服器保留事件序號與一段歷史,客戶端重連時帶上最後收到的序號、把缺口補回來——聊天訊息或協作編輯適合這條,因為每個事件本身有意義、漏掉就是資料不見。整份重抓(refetch from scratch)簡單得多,只要重新載入當前快照即可——即時儀表板、股價、線上人數適合這條,因為你只在乎最新值,中間漏掉的舊值本來就沒用。判斷方式其實接得上第 9 段的問題:這些推送是「事件」還是「最新狀態」?另外補一個作者沒提但實務必備的細節:重連要用指數退避,否則伺服器一掛,成千上萬的客戶端會同時重連把它再打掛一次。

術語:event replay

WebSocket

WebSocket

在單一 TCP 連線上維持雙向通訊的協定,讓伺服器可以主動推訊息給瀏覽器。

與 HTTP 輪詢的差別在於連線持續存在,不必為每則訊息付出建立連線的成本,延遲也低得多。代價是有狀態:伺服器要記住每條連線,水平擴展時需要額外的訊息匯流(例如 Redis pub/sub)讓不同機器上的連線也能收到同一則廣播。如果只需要伺服器單向推送,Server-Sent Events 更簡單、還自帶重連。

相關術語: Socket.IO (上層封裝)、event replay (斷線配套)

出處:第 10 段「即時同步與斷線後怎麼辦」

Socket.IO

Socket.IO

建立在 WebSocket 之上的函式庫,補上自動重連、房間廣播與退回輪詢等實務功能。

它不是 WebSocket 的別名,而是自己的協定,所以前後端必須都用 Socket.IO。它幫你處理掉的多半是「連線不穩」的長尾狀況:自動重連、心跳偵測、環境不支援時退回 HTTP 長輪詢。代價是訊息多一層封裝、且無法與原生 WebSocket 客戶端互通。

相關術語: WebSocket (建立於)

出處:第 10 段「即時同步與斷線後怎麼辦」

event replay

事件重播

客戶端重新連上線後,向伺服器索取斷線期間漏掉的那些事件並依序補上。

實作上需要事件帶有單調遞增的序號或時間戳,伺服器保留一段可回溯的緩衝,客戶端重連時送出「我最後收到 N」。適合每個事件本身都有意義的場景(聊天、協作編輯)。相對的做法是整份重抓,適合只在乎最新快照的場景(儀表板、報價)。兩者的取捨就是作者留下的那道追問。

相關術語: WebSocket (斷線後補償)

出處:第 10 段「即時同步與斷線後怎麼辦」

留給下一段 架構、資料與即時性都設計好了,但這一切要能持續進到正式環境才有價值——接下來是程式碼怎麼被安全地送上線。

11. CI、feature flag 與 A/B 測試 5:36–6:15

基礎建設與部署也要談:每次有人開 pull request,CI 就要跑 lint、測試與 build,這樣合併前大家才確定程式碼被檢查過。另一項是 feature flags 與 A/B testing——業務常需要漸進推出新功能,或比較哪個版本比較受使用者歡迎,資深工程師必須會實作。

一份 ci.yml 把「跑 lint、測試、build」展開成七個具體 step(含 type-check、e2e、lighthouse 效能預算),比口頭清單更能當面試時的檢查表。
5:50 · 一份 ci.yml 把「跑 lint、測試、build」展開成七個具體 step(含 type-check、e2e、lighthouse 效能預算),比口頭清單更能當面試時的檢查表。
YouTube 後台的 A/B 測試報告:三個標題縮圖的觀看時間佔比與 winner 標記,把 A/B testing 從抽象名詞變成「用數據選版本」的真實畫面。
6:08 · YouTube 後台的 A/B 測試報告:三個標題縮圖的觀看時間佔比與 winner 標記,把 A/B testing 從抽象名詞變成「用數據選版本」的真實畫面。
承上 承上:功能設計完了,這一段處理它怎麼被安全地送進正式環境。

推理因為前面所有設計都要靠一條可靠的發布流程才落得了地,所以作者把基礎建設與部署列進來:每次有人開 pull request,CI 就要跑 lint、測試與 build,這樣合併前大家才確定程式碼被檢查過。接著他把「安全上線」再往前推一步——feature flags 與 A/B testing:業務常需要漸進推出新功能,或比較哪個版本比較受歡迎,資深工程師必須會實作。

AI 補充第一張投影片把口述的三件事展開成一份 ci.yml 的七個步驟:`npm ci`(用 lockfile 安裝並吃快取)、lint、type-check、單元測試、build、e2e、以及 lighthouse 效能預算。最後那項特別值得注意——它把第 3 段和第 8 段談的載入效能變成 CI 會擋下來的硬指標,否則 bundle 的體積只會隨著時間單向惡化,沒有人會主動回頭修。第二張是 YouTube 後台的 A/B 測試報告:三個縮圖標題的觀看時間佔比(23.5%、43.3%、33.2%)與 winner 標記,這是他自己頻道的真實數據。這裡有個作者沒點破但很重要的關聯:feature flag 讓「部署」和「發布」脫鉤——程式碼可以先合併進 main 並部署,但對使用者關著,要開給誰、開多少比例是執行期的決定。這正好解決了第 4 段 micro-frontend 的一個隱憂:獨立部署要能獨立回退,而 flag 就是不用重新部署也能立刻關掉某功能的開關。代價是每個 flag 都是一條分支,清不掉的舊 flag 會讓程式碼難以推理,所以實務上要給 flag 設定到期日。

CI

持續整合

每次有人推送或開 PR 時,自動跑檢查與建置,確保程式碼隨時處於可合併狀態的實務。

重點不在「有跑」而在「擋得下來」——檢查必須是合併的必要條件,否則會變成沒人看的紅燈。速度同樣關鍵:超過十分鐘的 CI 會讓人開始想繞過它,所以要用快取與平行化壓時間,monorepo 更需要第 5 段提到的增量建置工具。投影片裡的 lighthouse 步驟展示了一個好習慣:把非功能性需求(效能)也變成會擋合併的檢查。

相關術語: pull request (觸發時機)、feature flag (接續於部署後)

出處:第 11 段「CI、feature flag 與 A/B 測試」

pull request

合併請求

把一條分支的改動提出來、經過自動檢查與人工審查後才併入主線的協作流程。

它同時是三件事的交會點:程式碼審查、CI 檢查、以及改動歷史的說明文件。PR 保持小是最有效的品質手段——大 PR 的審查品質會急遽下降,而 feature flag 正是讓大功能也能拆成一連串小 PR 合併的關鍵工具。

相關術語: CI (觸發)

出處:第 11 段「CI、feature flag 與 A/B 測試」

feature flag

功能開關

在執行期決定某功能是否對某些使用者開啟的開關,讓部署與發布脫鉤。

典型用途有三種:漸進推出(先開給 1% 使用者觀察錯誤率)、緊急關閉(出事不必回滾部署,關掉開關即可)、以及依對象開啟(內部員工、Beta 使用者)。工具有 LaunchDarkly、Unleash,也可以簡單自建。最大的長期成本是清理:每個 flag 都讓程式碼多一條分支,功能穩定後必須把 flag 和舊路徑一起刪掉。

相關術語: A/B testing (技術基礎)、CI (承接)

出處:第 11 段「CI、feature flag 與 A/B 測試」

A/B testing

A/B 測試

把使用者隨機分成數組、各看不同版本,用實際數據判斷哪個版本表現較好的實驗方法。

它靠 feature flag 做分流,但多了統計的要求:要事先決定衡量指標與樣本量,跑滿時間再看結果,否則很容易被隨機波動誤導成「有效」。投影片裡 43.3% 對 33.2% 的差距看似明顯,但若樣本不足同樣可能只是雜訊。前端工程師在這裡的責任是確保分流穩定(同一使用者每次都看到同一版)且埋點正確。

相關術語: feature flag (建立於)

出處:第 11 段「CI、feature flag 與 A/B 測試」

留給下一段 程式碼安全地上線了,但正式環境一定會壞——最後要處理的是壞掉的時候怎麼撐住並修好。

12. 可靠性:不要整頁白畫面 6:15–6:42

再來是可靠性:程式要能扛住錯誤,不能讓整頁掛掉。要攔截失敗、提供 fallback,而不是給使用者一片白畫面。而且正式環境一定會出 bug,所以需要能顯示 bug 發生、怎麼重現、stack trace、效能與使用者行為的工具。

ErrorBoundary 的完整實作:getDerivedStateFromError 換成 fallback、componentDidCatch 送去監控服務,最後註解示範「每個 micro-frontend 各包一層」——把可靠性接回第 4 段的拆分架構。
6:24 · ErrorBoundary 的完整實作:getDerivedStateFromError 換成 fallback、componentDidCatch 送去監控服務,最後註解示範「每個 micro-frontend 各包一層」——把可靠性接回第 4 段的拆分架構。
監控端的程式碼:unhandledrejection 帶上 userId 與 route,加上 PerformanceObserver 收 LCP / CLS,說明「能重現的錯誤報告」與「效能數據」具體要收什麼。
6:36 · 監控端的程式碼:unhandledrejection 帶上 userId 與 route,加上 PerformanceObserver 收 LCP / CLS,說明「能重現的錯誤報告」與「效能數據」具體要收什麼。
承上 承上:程式碼安全上線之後,剩下的問題是它在正式環境壞掉的時候會發生什麼事。

推理因為上線流程再嚴謹也擋不住所有錯誤,所以作者把可靠性當成最後一項技術主題:程式要能扛住失敗,不能讓整頁掛掉——要攔截失敗並給出 fallback,而不是丟給使用者一片白畫面。而且既然正式環境一定會出 bug,就需要能顯示 bug 發生、怎麼重現、stack trace、效能與使用者行為的工具。這兩句話對應到兩個動作:先撐住畫面,再收集足夠修好它的資訊。

AI 補充兩張投影片分別對應這兩個動作。第一張是 ErrorBoundary 的完整實作:`getDerivedStateFromError` 把畫面切換成 fallback、`componentDidCatch` 把錯誤送去監控服務,fallback 的文案寫的是「這一區塊載入失敗,頁面其他部分仍可使用」。最關鍵的是最後那行註解——每個 micro-frontend 各包一層 ErrorBoundary,這把可靠性直接接回了第 4 段的架構:既然各片段獨立部署,就必須獨立失敗,否則 B 團隊的一次壞部署會讓整站白畫面,獨立部署反而放大了風險。第二張是監控端:`unhandledrejection` 事件帶上 userId 與 route 一起回報,加上 PerformanceObserver 收集 LCP 與 layout-shift。這裡的重點是那行註解說的——有上下文(誰、在哪一頁)的錯誤報告才可能重現,只有一行 stack trace 的報告通常修不了。要補充的是 ErrorBoundary 的邊界:它只攔得到 React 渲染期間的錯誤,攔不到事件處理器裡、非同步回呼裡與 Promise rejection 的錯誤——後者正是第二張投影片用全域監聽補上的那一塊。兩者是互補的,不是二選一。

術語:error boundaryfallback UICore Web Vitals

error boundary

錯誤邊界

包住一段元件樹的 React 元件,當內部渲染拋出錯誤時攔下來、改顯示備用畫面而不是整頁崩潰。

它只攔截子元件在渲染、生命週期與建構函式中拋出的錯誤;事件處理器、setTimeout、Promise rejection 都攔不到,那些要靠 try/catch 或全域監聽。放置策略是分層:整頁一層當最後防線,再針對每個可能獨立失敗的區塊(側欄、推薦模組、第三方元件)各包一層,讓故障範圍等於該區塊。

相關術語: fallback UI (呈現手段)、micro-frontend (隔離單位)

出處:第 12 段「可靠性:不要整頁白畫面」

fallback UI

備用畫面

某區塊失敗時取而代之顯示的替代內容,讓頁面其餘部分仍然可用。

好的 fallback 有三個特徵:說明發生什麼事、提供可以做的動作(重試、回上一頁)、不誤導使用者以為資料是空的而不是壞的。「載入中」與「載入失敗」必須長得不一樣——把錯誤畫成永遠轉不完的 spinner 是很常見的失誤。

相關術語: error boundary (被觸發於)

出處:第 12 段「可靠性:不要整頁白畫面」

stack trace

呼叫堆疊

錯誤發生時的函式呼叫路徑紀錄,用來定位問題出在哪一行程式碼。

正式環境的程式碼經過壓縮,stack trace 會變成無意義的單字母,必須上傳 source map 給監控服務才還原得回原始檔名與行號(source map 不應公開給一般使用者)。而且光有 stack trace 通常不夠——要能重現還需要投影片裡那些上下文:哪個使用者、哪個路由、什麼瀏覽器、之前做了什麼動作。

相關術語: error boundary (由…回報)

出處:第 12 段「可靠性:不要整頁白畫面」

Core Web Vitals

核心網頁指標

Google 定義的一組使用者體驗量化指標,主要包含 LCP、CLS 與 INP。

LCP(最大內容繪製)量首屏主要內容多久出現,CLS(累積版面位移)量畫面亂跳的程度,INP(互動到下次繪製)在 2024 年取代了舊的 FID,量互動的反應速度。投影片用 PerformanceObserver 監聽 largest-contentful-paint 與 layout-shift,收集的就是真實使用者的數據(RUM),這比開發機上跑一次 Lighthouse 更有代表性——後者是實驗室數據,看不到真實網路與裝置的分布。這些指標會影響搜尋排名,所以也是能拿去跟業務溝通的語言。

相關術語: render blocking (劣化來源)

出處:第 12 段「可靠性:不要整頁白畫面」

留給下一段 技術面到此走完一輪。但一場面試講不完十三項——所以最後要回答的是:面對一道題,你要先挑哪一項來講?

13. 先問限制,再提方案 6:42–7:12

作者的收尾結論:資深工程師在面試裡最重要的不是先丟出解法,而是先問限制——業務到底要什麼、現在已經有什麼、我們實際上在解什麼問題。最後導向他的 middle to senior 前端訓練營。

承上 承上:十三項技術面向都掃過一遍之後,剩下的問題是面對一道具體的題目要先挑哪一項來講。

推理因為前面每一段都以「這要看情況/要看專案需求」收尾,所以作者在這裡把那個反覆出現的但書提升成整支影片的結論:資深工程師在面試裡最重要的不是先提出解法,而是先問限制——業務到底需要什麼、現在已經有什麼、我們實際上在解什麼問題。這正好閉合了第 1 段的開場:如果評的是思路而不是答案,那麼最能展現思路的一步,就是先證明你會界定問題。最後他把觀眾導向自己的中階到資深前端訓練營。

AI 補充這個收尾值得多花一點力氣理解,因為它是整支影片唯一可以直接照做的行為準則。前面每一項技術都附帶一個「什麼時候不該用」——micro-frontend 在小專案是純成本、Redux 在單純的 UI 狀態是樣板、CDN 對個人化內容有風險——這些但書全都指向同一件事:沒有限制條件,就沒有正確答案。所以先問限制不是禮貌,而是取捨的前提。實務上這三個問題可以直接照用:規模與限制是什麼(多少使用者、什麼裝置與網路、團隊幾人、要多快上線)、現況是什麼(既有技術棧、既有痛點)、以及真正要解的問題是什麼(業務指標是轉換率、留存還是開發速度)。問完之後你講的每個技術選擇都有依據,而不是在報名詞——這就是第 1 段說的「只是在回答問題」與展現思路之間的全部差距。

constraints

限制條件

決定哪個技術方案合理的外部條件:規模、期限、團隊人數、既有系統與業務目標。

system design 之所以沒有標準答案,就是因為限制條件不同。同一道「設計商品列表頁」,在三人新創(要快、要能改)和在千人電商(要能多團隊平行開發)會得出完全相反的架構。面試裡主動問限制有雙重效果:一是你的方案有了依據,二是它本身就是資深行為的證據——因為真實工作裡沒有人會把限制條件先寫好交給你。

相關術語: system design (前提)、senior developer (行為特徵)

出處:第 13 段「先問限制,再提方案」

留給下一段 總結收束。

4. 總結

作者從一個判準開始:資深面試考的不是你知不知道某個名詞,而是你會不會做取捨——所以整支影片的每一項技術都配一句「什麼時候不該用」。他沿著使用者的實際體驗往下推:打包成單一大檔會慢,於是用 code splitting 與 lazy loading 切成 chunk(切的是檔案);同樣一把刀往上抬到團隊層級就是 micro-frontend(切的是組織),拆開後緊接著要決定程式碼放在幾個 repo(monorepo 的一次改完 vs multi-repo 的團隊自主),以及執行期怎麼組回來(module federation 讓 host 從一個 CDN 網址載入對方的 remoteEntry.js,對方部署完就生效)。那個網址把主題帶到 CDN:距離就是延遲,東京打到維吉尼亞約 200ms,走當地邊緣節點命中快取約 20ms,配套問題是快取多久與怎麼失效——而答案接回了 chunk 的雜湊檔名。距離縮到最短後剩下檔案本身:tree shaking 靠 ES module 的靜態分析砍掉沒用到的匯出,所以 import 寫錯就完全失效;載入順序則靠 defer/async 避免 script 互相阻擋。程式碼跑起來後主題轉入 runtime:把資料分成 server state(真相在後端,用 React Query 管副本)、global UI state 與 local UI state,各用各的工具;如果資料是伺服器主動推的,就進入 WebSocket 的領域,斷線後要重播事件還是整份重抓,取決於推的是「事件」還是「最新狀態」。最後兩層把這一切撐住:CI 讓每個 PR 都跑過 lint、測試、build 與效能預算,feature flag 讓部署與發布脫鉤、也讓獨立部署的片段能獨立回退;error boundary 讓單一區塊的失敗不變成整頁白畫面(每個 micro-frontend 各包一層),監控則負責收集帶上下文的錯誤與 Core Web Vitals。整條鏈最後折回起點:既然每個環節都是取捨,正確的第一步就不是給答案,而是先問業務要什麼、現在有什麼、真正要解的是什麼問題。

勘誤總整理

確定錯誤/已過時見仁見智(取決於版本或情境)

段落原話(transcript 逐字)說明
8. Bundle 最佳化與瀏覽器載入順序
3:37
「We obviously have tree shaking on the client」tree shaking 不發生在 client(瀏覽器),而是在建置期由打包工具做靜態分析後移除未使用的匯出——產物送到瀏覽器時已經是砍過的。這個區分很重要:正因為它是建置期的靜態分析,`require` 或整包 import 這種要到執行期才知道用了什麼的寫法才會讓它失效,也就是作者下一句自己提到的坑。
依據: webpack 官方文件 Tree Shaking 章節將其定義為 bundler 在 build 階段依 ES module 靜態結構移除 dead code
7. CDN 與快取:距離就是延遲
3:26
「it may take just 2 milliseconds」口述說命中快取只要 2 毫秒,但他自己的投影片寫的是約 20ms。2ms 對真實的網路往返(含 TLS 與最後一哩)幾乎不可能達到,即使邊緣節點就在同一座城市;20ms 才是同城命中快取的合理量級。面試時引用這個數字建議說「數百毫秒降到數十毫秒」,不要說 2ms。
依據: 同段投影片(影片 03:27)標示 Tokyo → Edge node → Cache hit ≈ 20ms
9. State management:三種 state
4:42
「For global UI state you might want to use Redux」把 Redux 當成全域 UI 狀態的預設選擇在 2026 年已偏保守。純 Redux 的樣板量大,官方本身也推薦改用 Redux Toolkit;而純 UI 狀態的新專案多半直接選 Zustand 或 Jotai(作者自己的投影片就把 Zustand 排在 Redux 前面)。Redux 仍有優勢的場景是需要嚴謹的 action 紀錄、時間旅行除錯,或團隊已有大量既有 Redux 程式碼。
依據: 同段投影片標示「Use: Zustand / Redux」,Zustand 在前;Redux 官方文件自 2021 起以 Redux Toolkit 為建議寫法

5. 推薦三個下一步

1. 往下挖深:把 micro-frontend 與 module federation 真的做一次

影片只用一張架構圖和一段 webpack 設定帶過,但真正的難點——共用相依的版本協商、跨片段的路由與登入狀態、遠端掛掉時的 fallback——一個都沒展開。這是整條推理鏈裡代價最高、最容易做錯的一環。

YouTube 搜尋:module federation tutorial webpack 5 micro frontend shared dependencies pitfalls micro frontend routing authentication

2. 往旁邊對照:後端的 system design 怎麼談同一組問題

快取失效、獨立部署、故障隔離這些概念都是從後端搬過來的,但後端多了資料一致性與流量控制這兩個前端沒有的維度。看過後端版本,才知道面試裡「前端 system design」的邊界劃在哪、什麼時候該說「這一段歸後端」。

YouTube 搜尋:system design interview caching strategies microservices vs monolith tradeoffs CDN cache invalidation strategies

3. 往上應用:把載入效能變成可量測、會擋合併的指標

影片提到 bundle analyzer 與 CI 裡的 lighthouse 步驟,但沒說怎麼設門檻、怎麼在真實使用者身上量。缺了這一步,前面所有效能設計都無法證明有效,也擋不住體積隨時間單向惡化。

YouTube 搜尋:core web vitals real user monitoring performance budget CI lighthouse webpack bundle analyzer reduce bundle size

📄 全部影片 · 主題區: 前端底層與系統設計