另外是一些關於這次架構轉換的技術性細節。
簡單來說,mosir的前端架構從Next.js 16換成了Tanstack Start。但背後有除了架構偏好之外的更多原因。
Nextjs的安全風險
Nextjs在2025年3月爆出header injection漏洞(CVE-2025-29927),2025年底也受到RSC上游的remote code executiton漏洞影響(CVE-2025-66478)。
雖然mosir受益於安全隔離架構設計,不允許SSR接觸任何與認證或隱私相關的資料,並沒有受到任何影響,但Nextjs專案的走向與App Router的RSC-first思維在我看來並不是很合理,這是決定進行架構轉換的重要因素之一。
實際上在架構切換時App Router又發生另一個阻斷攻擊漏洞(CVE-2026-23870),可以預期未來會有更多問題發生。
有缺陷的React Server Component Mindset
React Server Component(RSC)是mosir從開發初期就大量使用的技術。相較於其他SSR架構,RSC比較容易由架構本身最佳化,而且可以預期未來會是React團隊的主要開發重點。
但歷經了15個月,RSC及Nextjs帶來的問題比解決的問題還多。
首先,Nextjs用來配合RSC的Server Action在SSR過程中很難確保執行邊界。Server Action使用在封裝Server端用來存取敏感資料(例如密碼驗證)時還是有很明顯的優勢,但mosir的安全性設計在一開始就把安全驗證完全交給backend API server,Server Action的優勢並不明顯。相較之下,Server Action只能在RSC環境或Client環境執行這點在開發時帶來非常多困擾。最明顯的狀況是auth cookie更換完全無法在SSR client component內呼叫,只能夠在RSC進到client component前先更新完,或是用useEffect強制在browser呼叫遠端Server Action操作。搭配client component無環境感知的設計讓mosir在處理登入狀態更新時需要撰寫異常大量的程式碼處理各種極端狀況。多餘的程式也造成許多難以修復的bug。
而Turbopack的執行狀況一直都不算理想。在Nextjs 15前的頁面載入至少還是直接存取prebuilt assets。但Nextjs 15之後由turbopack接手,載入行為變得異常詭異。在大多數時候js assets是由turbopack在runtime現場生成的。雖然turbopack的效能很優秀,但執行時生成靜態資源還是需要更多CPU usage,也造成更多memory leak。
同時Nextjs讓mosir鎖死在Node生態系無法轉換。平心而論,Node的event loop設計比很多架構成熟也優秀得多(嗯,我是指Python Async)。但跟mosir的Golang後端比起來那個速度真的是慢到很可笑。新工具像bun或deno可以輕鬆達成翻倍吞吐量、減半反應時間。雖然跟Golang比起來還是有點差距,至少是比較合理的選擇。mosir從開發初期就很努力想要嘗試切換到bun,可惜Nextjs對bun的支援度一直無法提升。這點在上線後嚴重影響伺服器效能。
而且Nextjs Streaming SSR非常無法預測。App Router繼承了Page Router的loading.tsx設計,官方文件也多半推薦使用loading.tsx作為SSR FCP邊界。雖然同時支援React Suspense,但文件沒有明確說明行為,而且實測行為也很難預測。這讓mosir頁面在初始載入時多半只能顯示Skeleton,這點嚴重影響了SEO,並且造成大量頁面閃爍問題。
最後,Server-First Routing對App-liked介面與動態內容完全不合理。以mosir的狀況來說,大部分頁面都是文章本身、個人檔案、作品集頁面,元件是可以完全共用的。但在Nextjs的Server-First Routing mindset底下每個連結都必須經過SSR Server確認。搭配有缺陷的preload設計,Nextjs讓mosir在顯示一篇文章時就必須跟SSR Server確認頁面是否存在,畫面上如果有20篇文章,就必須發送20個requests。考慮到文章連結都是相同的,SSR回傳的Skeleton也沒有提供任何有用資料,這些requests只是在浪費網路流量確認文章是否存在而已。搭配大量使用store的後果是preload只剩下確認頁面需要哪些JS assets,而這些assets在同個route底下根本不可能變動。