比較対象はNext.jsやその他Reactフレームワークであり、そのようなフレームワークよりも「Vite + React」というシンプルな構成を好む意見です。
現実とは、作者が「Vite + React」を維持したままRSC (React Server Components) を活用できる新たなフレームワークFUNSTACK Staticを開発し、先般1.0.0をリリースしたことです。
Vite + React是把 React 當作單純的前端函式庫來使用,並直接使用 Vite 作為建置工具的組合。
最大的特徵是,它是一個以 CSR(Client Side Rendering,用戶端渲染)的 SPA(單頁應用程式)為主。也就是說,建置輸出是 HTML、CSS、JS 等資產,部署到靜態檔案伺服器即可運作。
換言之,無需在伺服器上部署 Node.js 等環境運行,也不一定要使用 Vercel 之類的平台。
此外,因為不使用框架,沒有(或只有很少的)規約,開放度很高,這也是 Vite + React 的特色。也就是說,程式如何運作完全由開發者決定,沒有框架在背後替你自動處理什麼。
所謂的規約,例如檔案系統為基礎的路由(file-system based routing)。也就是說,若按照框架的規則命名及放置檔案或目錄,系統會自動對應出相對應的 URL。在 Vite + React 中,這些要由開發者自行處理,因此通常會引入適用於 SPA 的路由函式庫等。
「Vite + Reactでいい」這句話背後的一個原因,是 Vite + React 與其他 React 框架之間存在著相當大的差距。正如先前的文章所述,框架雖然能以多種方式使用,但典型上會有下列差異:
| 比較項目 | Vite + React | 框架(例如 Next.js) |
|---|---|---|
| 伺服器 | 不需要 | 需要 |
| SSR(伺服器端渲染) | 無 | 有 |
| RSC(React Server Components,需注意 "use client" 標記) | 無 | 有 |
| 規約 / 獨有功能 | 無 | 有 |
也就是說,差別包含需要能執行 JavaScript 的伺服器(如 Node.js)、近年的框架多以 RSC 為基礎、以致傳統做法無法直接沿用,以及必須理解框架特有的規約與功能(例如 Next.js 的各種快取機制)等。
由於在許多情況下框架無法沿用傳統的「Vite + React」思維,便有一群人對框架保持距離,偏好簡單、無伺服器依賴的 Vite + React。
在這些差異中,「有無伺服器」尤其難以忽視。RSC 或框架規約之類的問題,通常屬於「學習成本」或「鎖定(lock-in)」的問題,作者個人並不認為那是最大問題;但如果不需要架設伺服器,那就不架設總是更省事。
因此,在不想額外架伺服器的場景下,Vite + React 被推薦為合適的技術棧;同時這也導致 SSR 或 RSC 常常被一併否定。
此處轉到作者所開發的 FUNSTACK Static。更詳細的介紹可參考下述文章。
要點是,FUNSTACK Static 是一個框架,但仍堅持「Vite + React」的使用方式,不需要伺服器,卻採用了 RSC。因此,它在傳統的「Vite + React」與其他框架之間佔據中間位置。
| Vite + React | FUNSTACK Static | 其他框架 | |
|---|---|---|---|
| 伺服器 | 不需要 | 不需要 | 需要 |
| SSR | 無 | 有(選擇性) | 有 |
| RSC("use client") | 無 | 有 | 有 |
| 規約 / 獨有功能 | 幾乎沒有 | 幾乎沒有 | 有 |
另外,FUNSTACK Static 名為框架,但實際上是以 Vite 外掛(Vite plugin)實作,因此沒有獨立的 CLI。開發時使用下列 Vite 的指令:
vite dev # 啟動開發伺服器
vite build # 建置
這看起來就像 Vite + React。因為無需伺服器,建置輸出仍然是 HTML、JS 等靜態檔案。
規約與獨有功能方面也是「幾乎沒有」。規約部分只要求「準備兩個檔案,且各自以 default export 匯出元件」。另外,這兩個檔案的檔名是可自訂的。
// root.tsx
export default function Root({ children }) {
return (
<html>
...
<body>{children}</body>
</html>
);
}
// app.tsx
export default function App() {
return (
<div>My App!</div>
);
}
目前唯一提供的獨有功能是名為 defer 的函式:
import { defer } from "@funstack/static/server";
root.tsx 相當於把 Vite + React 的 index.html 改成用 JSX 撰寫,而 app.tsx 則是應用程式的入口。這並沒有讓 Vite + React 的使用變得複雜。defer 則是為了某種像是 bundle 大小優化的目的而存在的功能。
FUNSTACK Static 最顯著的特性是它是以 RSC 為基礎,這是與傳統 Vite + React 的主要差異。
也就是說,會有「要在哪裡加 "use client"」、「不可從客戶端 import 伺服器模組」等規則需要遵守。
要善用 FUNSTACK Static,就必須熟悉 RSC,這是與以往 Vite + React 之間的落差之一。
那麼為何以 RSC 為基礎?原因在於,RSC 可用來改善 SPA 的效能。如先前文章已說明,這裡不再贅述;重點是即便沒有伺服器,也仍能在某種程度上利用 RSC。
與 Vite + React 相比,伺服器元件(server components)可以做檔案讀取等操作,這是一大優勢。過去需要在建置時透過腳本或 Vite 外掛來達成的事情,可能會以更簡潔的 JavaScript/TypeScript 程式碼實現。
FUNSTACK Static 的主軸,就是把 RSC 的優點帶入「無伺服器的 SPA」。
文章標題所指的「擺在面前的現實」,不言可喻,就是 FUNSTACK Static 的出現。
說「Vite + Reactでいい」的人,必須重新思考自己這句話究竟代表什麼意思。
重申之前的比較:由於傳統的 Vite + React 與其他框架存在相當差距,「Vite + Reactでいい」包含了許多隱含的前提與可能性──是因為不喜歡有伺服器嗎?是不喜歡 RSC 嗎?還是討厭被框架規定檔名?等等。
| Vite + React | FUNSTACK Static | 其他框架 | |
|---|---|---|---|
| 伺服器 | 不需要 | 不需要 | 需要 |
| SSR | 無 | 有(選擇性) | 有 |
| RSC("use client") | 無 | 有 | 有 |
| 規約 / 獨有功能 | 幾乎沒有 | 幾乎沒有 | 有 |
FUNSTACK Static 出現在兩者之間。它能直接使用 Vite 的指令、能以靜態檔案伺服器托管、沒有檔名強制規則,而同時支援 RSC。
因此,說「Vite + Reactでいい」的人,必須思考:對方是不是能接受 FUNSTACK Static?還是也排斥它?為此需要更高解析度地理解自己為何認同「Vite + Reactでいい」。
特別是「有無伺服器」這一大差別,過去是使「Vite + Reactでいい」具說服力的重要因素。當然,某些應用確實比較適合有伺服器的架構,但也有不需要伺服器的情境。伺服器是否存在是與基礎設施等設計選擇相關的問題。
當這個因素不再是分水嶺時,剩下的討論便成為原始碼層級的議題──是否採用 RSC、學習成本、維護性、程式碼架構等。
老實說,作者認為 RSC 常被過度迴避,而「有無伺服器」經常被拿來當成藉口。作者希望能消除這個藉口,讓討論回到 RSC 本身的利弊上。
「兩個選擇」指的當然是,是否把 FUNSTACK Static 視為「Vite + Reactでいい」的一員。
從工具使用方式與輸出結果看,FUNSTACK Static 顯然屬於 Vite + React 的範疇。但它與傳統 Vite + React 最大的差異在於採用了 RSC。
如果你的真實想法是「我不想要 RSC」,那麼以後應該直接說「我不想使用 RSC」,而不是籠統地說「Vite + Reactでいい」。
如果你的立場是「只要能輸出不需伺服器的 SPA,我就願意使用 RSC」,那麼說「Vite + Reactでいい」仍然是合理的。
當然,作者傾向後者。RSC 能改善效能,特別是降低客戶端負載,這對 SPA 的開發相當重要。
本文宣傳了作者開發的 FUNSTACK Static,並指出「Vite + Reactでいい」這個立場其實可能分裂為兩種不同的取向。
題外一提,作者在今年一月開始開發 FUNSTACK Static。雖然如今能用在休閒時間的時間比以前少很多,但仍能投入 OSS 開發,多虧了 AI 的幫助。不過,要從頭打造支援 RSC 的 React 框架,對於現有的 AI 來說仍有難度,最核心的部分仍是以溫暖的人手工寫成的。