我們最常被問到的問題大概是:
Remix 和 Next.js 有什麼不同?
看來我們必須回答這個問題!我們希望直接且不帶戲劇性地解決這個問題。如果你是 Remix 的粉絲,並且想要開始針對這篇文章發布自鳴得意的推文,我們懇請你在按下推文按鈕之前放下你的自鳴得意 🤗。水漲眾船高。早在 Vercel 成立之前,我們就已經和 Vercel 的朋友們是朋友了。他們做得很好,我們尊重他們所做的工作!
但請不要誤會,**我們認為 Remix 比 Next.js 有更好的權衡取捨**。(不然我們就不會開發它了...)
我們鼓勵您閱讀完整篇文章。在這個對話中有很多細微之處,是那些閃亮的圖表和動畫無法捕捉的。希望到最後,您會考慮在您的下一個專案中使用 Remix(絕非雙關語 😂)。
我們認為比較框架最公平的方式,是採用 Vercel 團隊自己編寫的 Next.js 範例應用程式。由於是他們自己編寫的,因此他們所做的決策應該能反映出他們希望您如何建置應用程式。它也應該展示 Vercel 團隊最引以為傲的功能。
我們從 Next.js 的範例頁面中移植了 _Commerce Example_。它有一些我們喜歡的真實世界功能,而且似乎是他們投入最多心力的範例。
我們實際上建立了兩個版本
請注意,此應用程式無法展示我們認為 Remix 很酷的所有功能(例如巢狀路由!)。一旦我們回答了這個問題,我們就可以繼續只談論 Remix,敬請期待!
此外,我們在發佈之前與 Vercel 分享了這篇文章。結果發現他們的範例是在舊版本的 Next.js 上執行的,他們更新了它,因此我們花時間重新製作此範例,以便與他們的最新範例進行比較。
我們將他們視為朋友,甚至是合作夥伴,因為 Vercel 是 Remix 的絕佳部署目標。我已經將 Remix 應用程式部署到我聽過的幾乎所有託管服務。Vercel 的開發人員體驗絕對是我的最愛。「開發、預覽、發布」的座右銘具有實際效果。就在今天早上,@gt_codes 和我試圖找出一個生產錯誤,而每次預覽部署都附帶一個小螢幕截圖,這幫助我們在幾秒鐘內找到錯誤的提交。這很棒。
現在的關係很有趣,因為我們不僅僅是朋友和技術合作夥伴,我們還是框架競爭對手!因此,對於我們的朋友、合作夥伴和競爭對手 Vercel,Lee 很棒地說明了撰寫這篇文章的動機:
當開發工具存在競爭時,開發人員會獲勝
— Lee Robinson (@leeerob) 2021 年 11 月 30 日
◆ Svelte 正在推動 React
◆ Remix 正在推動 Next.js
◆ Prisma 正在推動 ORM
◆ Deno 正在推動 Node.js
◆ Supabase 正在推動 Firebase
◆ esbuild / SWC 正在推動 JS 工具
◆ Bun 正在推動 SWC
還有什麼?
請在此背景下閱讀這篇文章。讓我們開始推動!
我認為您可以從建置它的人如何描述它來了解很多東西。(如果您在 Twitter 上關注我,您就會知道我一直在努力地迭代我們的描述!)
Next.js 將自己描述為
適用於生產的 React 框架。Next.js 為您提供最佳的開發人員體驗,並具備生產所需的所有功能:混合靜態和伺服器渲染、TypeScript 支援、智慧綁定、路由預取等等。無需配置。
Next.js 由 Vercel 建置。檢視 Vercel 平台的 GitHub 儲存庫,它指出
Vercel 是一個用於靜態網站和前端框架的平台,旨在與您的 headless 內容、商務或資料庫整合。
我們將 Remix 描述為
Remix 是一個原生 edge 的全堆疊 JavaScript 框架,用於建置現代、快速且具有彈性的使用者體驗。它使用網路標準統一了客戶端和伺服器,讓您可以減少對程式碼的思考,而更多地思考您的產品。
我們將讓您比較這些描述。
Remix 和 Next.js 一樣快嗎?
這通常是人們問的第一個問題。Next.js 經常使用「預設效能」這個詞組,而且他們確實擁有優異的效能!讓我們看看每個應用程式渲染「視覺上完整」的頁面有多快。
我們使用 WebPageTest 執行網站測試。這是一個很棒的工具,可以產生這篇文章中的比較 GIF。在每次比較中,我們為每個框架執行五次,並取每個框架中最好的一次。
每個比較上方都有一個標題,其中連結到產生動畫的結果。您可以透過簡單地在 WebPageTest.com 上點擊「重新執行測試」來自行驗證所有內容。
第一次測試是從維吉尼亞使用有線數據機連線到網際網路執行的。
在我們說任何話之前,我們先承認這三個版本都非常快,甚至不值得比較誰更快。對 Next.js 也有點不公平,因為 Cookie 橫幅的小動畫會影響「視覺上完整」,而 Remix 網站沒有這個。讓我們以慢動作檢視它
現在我們可以發現,Next.js 實際上在 0.8 秒時完成。再一次,它們都很快。我也在 3G 網路連線的相同測試中執行了它們,結果相同:都很快,看起來都差不多。
✅ Remix 與 Next.js 一樣快
為什麼 Next.js 很快:首頁使用具有 getStaticProps
的靜態網站產生 (SSG)。在建置時,Next.js 從 Shopify 提取資料,將頁面渲染為 HTML 檔案,並將其放入 public 目錄中。當網站部署時,靜態檔案現在會在 edge(從 Vercel 的 CDN 中)提供服務,而不是在單一位置點擊原始伺服器。當請求傳入時,CDN 只是簡單地提供檔案。資料載入和渲染已經提前完成,因此訪客不需要支付下載 + 渲染成本。此外,CDN 是全球分散的,靠近使用者(這就是「edge」),因此靜態產生文件的請求不需要一直傳送到單一原始伺服器。
為什麼 Remix 移植版本很快:Remix 不支援 SSG,因此我們使用了 HTTP stale-while-revalidate 快取指令(SWR,不要與 Vercel 的 swr
客戶端提取套件混淆)。最終結果是相同的:edge 上的靜態文件(即使在相同的 CDN Vercel 上)。不同之處在於文件如何到達那裡。
當您獲得流量時,快取會被初始化,而不是提取所有資料並將頁面渲染為建置/部署時的靜態文件。文件會從快取中提供,並在背景中重新驗證,以供下一位訪客使用。與 SSG 一樣,當您獲得流量時,沒有訪客需要支付下載 + 渲染成本。如果您想知道快取未命中,我們稍後會討論一下。
SWR 是 SSG 的絕佳替代方案。部署到 Vercel 的另一個好處是他們的 CDN 支援它。
您可能會想知道為什麼 Remix 移植版本沒有 Next.js 快。由於 Remix 沒有內建的影像最佳化功能(尚未),我們只是將影像指向 Next.js 應用程式 🤫。瀏覽器必須開啟與兩個網域的連線,這會使影像載入延遲 0.3 秒(您可以在 網路瀑布圖上驗證這一點)。如果影像是由自己託管的,則它會與其他兩個應用程式一樣快,大約在 0.7 秒左右。
為什麼 Remix 重寫版本很快:這個版本不是使用 SSG 或 SWR 在 edge 快取文件,而是在 Redis 中在 edge 快取資料。事實上,它實際上也使用 Fly.io **在 edge 執行應用程式**。最後,它有一個快速的影像最佳化 資源路由,該路由會寫入到 永久磁碟區。它基本上是自己的 CDN 😎。
這在幾年前可能很難建置,但伺服器環境在過去幾年中發生了顯著的變化,而且只會變得越來越好。
Remix 與 Next.js 有何不同?
這是我們接下來被問到的問題。功能集有很多差異,但一個主要的架構差異是 Remix 不依賴 SSG 來提高速度。
在幾乎每個應用程式中,您最終都會遇到 SSG 無法支援的情況。對於我們在此處比較的應用程式,它是搜尋頁面。
限制是使用者可以提交無限數量的查詢。在宇宙目前對空間和時間的限制下,您無法靜態產生無限的頁面。SSG 被排除在外。
由於 SSG 無法擴展到動態頁面,Next.js 從使用者的瀏覽器切換到客戶端資料提取。檢視網路瀑布圖將告訴我們為什麼它比 Remix 慢 2.3 倍。
Remix 應用程式在 Next.js 應用程式開始載入圖片之前就已完全完成。在網頁效能方面,最重要的事情或許是將網路瀑布平行化。在 Remix,我們對此非常著迷。
為什麼 Next.js 比較慢:Next.js 引入了我們所謂的「網路瀑布請求鏈」。由於這裡無法使用 SSG,應用程式會從使用者的瀏覽器獲取搜尋結果。在它獲取資料之前,無法載入圖片;而在它載入、解析和評估 JavaScript 之前,也無法獲取資料。
在客戶端獲取資料也意味著網路上會有更多 JavaScript,以及更多的解析/評估時間。有時候我們會忘記解析/評估,但你可以看到第 15 個請求的 JS 執行時間比文件下載的時間還要長!Next.js 發送的 JavaScript 比 Remix 多 1.5 倍,解壓縮後為 566 kB,而 Remix 為 371 kB。在網路上,壓縮後 Next.js 多了 50 kB (172 kB vs. 120 kB)。
在瀏覽器中做更多的工作會開始累積。看看底部顯示 CPU 使用率和瀏覽器主執行緒活動的列。Next.js 應用程式相當忙碌,有一個很大的紅色「長時間工作」拖慢了速度。
為什麼 Remix 仍然和首頁一樣快:實際上,這兩個 Remix 範例都不需要在請求中與 Shopify API 通訊。雖然 SSG 無法快取搜尋頁面,但 Remix 版本可以:使用 SWR 或 Redis。當你有一個單一、動態的方式來產生頁面時,你可以在不更改應用程式碼的情況下調整快取策略。結果是在常被訪問的頁面上達到 SSG 的速度。"/search"
頁面很可能會被預先載入,左側導覽列的類別和常見查詢(如「tshirt」)也是如此。
是啊,但是如果快取未命中呢?
你可能不會相信我,而且我無法證明我們的快取是空的,但這是一個 Remix 中的快取未命中 (我發誓,如果說謊就讓我遭天譴)。
實際上,我說謊了。這是 Remix Rewrite 的快取命中。快取未命中更快 (0.6 秒🤭)。我真的不認為你會相信我,所以我在圖表中放了較慢的快取命中😅
不可能!
事實證明 Shopify API 相當快。
由於 Next.js 應用程式是直接從瀏覽器獲取 Shopify API 的資料,我們可以查看測試的網路圖,並看到請求只花了 224 毫秒。瀏覽器與 API 建立連線的時間比發出請求的時間還要長!(他們可以在初始 HTML 中使用 <link rel="preconnect" />
來加快速度。)
如果使用者的瀏覽器可以這麼快地向 Shopify 發出請求,那麼 Remix 伺服器肯定可以更快地完成。使用者與雲端的連線速度總是比你的伺服器慢,最好將資料獲取保留在那裡。
最重要的是,在使用 Shopify API 時,快取幾乎沒有意義。快取命中或未命中實際上沒有區別。
最好的說明方式是減慢使用者的網路速度,看看會發生什麼事。讓我們再來一次快取未命中,這次是從香港使用 3G 連線。
即使在快取未命中的情況下,Next.js 現在也落後了 3.5 秒。怎麼回事?
你不是說 Shopify API 很快嗎!
Next.js 在載入資料之前無法載入圖片,在載入 JavaScript 之前無法載入資料,並且在載入文件之前無法載入 JavaScript。使用者的網路速度是該鏈中每一步的乘數 😫。
在 Remix 中,唯一的鏈是等待文件能夠載入圖片。Remix 設計成總是在伺服器上獲取資料,消除了使用者網路在其他所有地方的乘數效應。
Remix 可以在收到請求時立即開始從 Shopify 獲取資料。它不必等待瀏覽器下載文件和 JavaScript。無論使用者的網路速度有多慢,伺服器上對 Shopify API 的獲取速度都不會改變,可能在 200 毫秒以下。
當 Next.js 轉向在客戶端獲取資料時,使用者體驗並不是唯一受到影響的。應用程式現在有兩組不同的抽象概念來與 Shopify 通訊:一組用於 SSG,另一組用於瀏覽器。
像這樣的架構分歧會引發一些主要問題
process.env
嗎?window.location.origin
嗎?(我的天啊,我說了同構)(這與這篇文章無關)(啊!PROFUNCTOR OPTICS!)
讓我們為 Remix 回答這些問題,你只需要在伺服器上抽象化 Shopify API 即可
process.env
嗎?(可以)window.location.origin
嗎?(不行)這些問題的答案越簡單,你的抽象化就越好,進而產生更簡單的程式碼來使用。
如果 Next.js 應用程式放棄客戶端獲取,並使用 getServerSideProps
,他們可能會縮小差距,並對這些問題有更簡單的答案。有趣的是,Next.js 文件通常會建議你不要使用伺服器獲取,而改用 SSG 或客戶端獲取
如果你不需要預先渲染資料,那麼你應該考慮在客戶端獲取資料。
他們也鼓勵客戶端獲取包含使用者資料的頁面,再次推動你走向更多的架構分歧。
例如,[客戶端獲取]適用於使用者儀表板頁面。由於儀表板是私有的、使用者特定的頁面,因此 SEO 並不重要
正如我們在這裡看到的,伺服器渲染也關乎更好的效能,而不僅僅是 SEO。
這裡的根本區別在於 Next.js 有四種在頁面上獲取資料的「模式」
getInitialProps
- 在伺服器和客戶端呼叫getServerSideProps
- 在伺服器端呼叫getStaticProps
- 在建置時呼叫Remix 只有一種:loader
。圍繞一個僅在一個地方執行的東西進行抽象,比圍繞三個地方執行的四個東西更容易。
讓我們試著量化這種架構分歧的成本。這個應用程式最困難的開發任務或許是抽象化商務後端。應用程式的設計方式是,你可以將任何東西插入其中:Shopify、BigCommerce、Spree、Saleor 等。
在 Next.js 應用程式中,Shopify 整合位於這個資料夾。今天對它執行 cloc
會產生
101 text files.
93 unique files.
8 files ignored.
github.com/AlDanial/cloc v 1.92
---------------------------------------------------------------------
Language files blank comment code
---------------------------------------------------------------------
TypeScript 88 616 2256 5328
GraphQL 1 1610 5834 2258
Markdown 1 40 0 95
JSON 2 0 0 39
JavaScript 1 1 0 7
---------------------------------------------------------------------
SUM: 93 2267 8090 7727
---------------------------------------------------------------------
將近 100 個檔案中將近 8,000 行程式碼。我對其他整合執行了它,情況也相同。它們都接近 100 個檔案,並且徘徊在 10,000 行程式碼左右。幾乎所有這些程式碼也都會進入瀏覽器。
這就是架構分歧的成本。Next.js 抽象化必須預期並參與建置和瀏覽器。Remix 抽象化僅存在於伺服器上。
你可能會想知道這兩個 Shopify 提供者是否具有相同的功能集,以及我們是否在欺騙。它們中的許多程式碼都包含身份驗證和願望清單,但 Shopify 提供者未使用其中任何一個(但確實必須匯出它們的模組)。使用這兩個網站,它們似乎具有相同的功能集。無論如何,如果我們確實遺漏了某些東西,很難想像當應用程式中可見的功能只需要 1/10 的程式碼時,達到那個程度需要 7,000 行程式碼。
即使 Next.js 為搜尋頁面改用 getServerSideProps
,他們仍然需要幾乎所有這些程式碼來實現資料修改功能,但我現在有點超前了!
我們經常談論「部署到邊緣」。那是什麼意思?這是另一個來自香港的快取未命中,這次使用快速的使用者網路
這次我們要談論兩個 Remix 應用程式之間的差異。我們已經知道 Next.js 版本由於網路瀑布鏈而速度較慢。
兩個 Remix 應用程式都在伺服器上獲取資料,那麼為什麼 Remix port 會遠遠落後於 Remix Rewrite?
答案很簡單:Remix Port 是在 Vercel 函式中執行的,而 Vercel 的函式並不會在邊緣節點執行你的程式碼,它們會在一個區域執行,預設是華盛頓特區。這離香港相當遠!
這表示使用者必須從香港一路連線到華盛頓特區,伺服器才能開始從 Shopify 提取資料。伺服器完成後,還必須將文件一路傳送回去。
Remix Rewrite 也是在華盛頓特區執行,但它也在香港執行!這表示使用者可以很快連線到 Remix 伺服器,所有事情都會更快。
這就像騎自行車到火車站搭車進城,而不是一路騎自行車進城。
🚲-----------------------------------------🏢
🚲-----🚊====🏢
你可以像平常一樣在網路瀑布圖中看到這個情況
基礎架構的差異體現在文件的第一個藍色長條。在 Remix Port 中,它明顯大得多。那是使用者在 Vercel 函式的自行車道上騎著自行車環遊半個世界。在 Remix Rewrite 中,它搭上了火車,更快地到達了 Shopify API 並返回。
這個版本在 Fly.io 上執行,它可以在全球數十個區域執行 Node.js 伺服器。不過,Remix 並不依賴 Node.js。它可以運作於任何 JavaScript 環境。事實上,它已經在 Cloudflare Workers 中執行,這表示你的程式碼是在 他們遍布全球的 250 個伺服器上執行。沒有比這更靠近使用者的了!
這就是為什麼我們說 Remix 是「邊緣原生」的。Next.js 依賴 Node.js,因此目前其部署到邊緣節點的能力有限。
我們在這個領域還有很多工作要做,以改善開發人員體驗。我們目前僅正式支援 Node.js 和 Cloudflare,但我們正在積極開發 Deno,而且社群成員已讓 Remix 在 Fastly 上運行。
當你使用像 Remix 這樣的「邊緣原生」框架時,你不再需要決定哪些使用者獲得更快的體驗。你可以讓每個使用者都能獲得快速體驗,無論他們身在世界何處。
邊緣節點是 Remix 的建構基礎。如你所見,它非常有潛力。據我們了解,Vercel 團隊也在努力將你的應用程式部署到邊緣節點。Remix 已經準備好了,我們迫不及待想試試看。
這兩個框架都可以透過連結預先載入來實現即時轉換,但 Next.js 只會針對從 SSG 建立的頁面執行此操作。搜尋頁面又出局了。(也許下次吧,老兄)
然而,Remix 可以預先載入任何頁面,因為資料載入沒有架構上的分歧。 預先載入一個不可知的、由使用者驅動的搜尋頁面 URL 與預先載入一個可知的產品 URL 沒有任何不同。
事實上,Remix 的預先載入不僅限於連結,它可以隨時隨地、以任何理由預先載入任何頁面!看看這個,在使用者輸入時預先載入搜尋頁面
沒有旋轉符號,沒有骨架畫面,即使在慢速網路上也能提供即時的使用者體驗 🏎
這也很容易做到。
import { Form, PrefetchPageLinks } from "@remix-run/react";
function Search() {
let [query, setQuery] = useState("");
return (
<Form>
<input type="text" name="q" onChange={(e) => setQuery(e.target.value)} />
{query && <PrefetchPageLinks page={`/search?q=${query}`} />}
</Form>
);
}
由於 Remix 使用 HTML 的 <link rel="prefetch">
(而不是像 Next.js 那樣的記憶體內快取),因此實際上是由瀏覽器發出請求,而不是 Remix。觀看影片時,你可以看到當使用者中斷目前提取時,請求是如何被取消的。Remix 不需要為了處理非同步作業而傳送任何程式碼。#使用平台... 或,呃,#重複使用平台 😎?
這就是 Remix 和 Next.js 開始看起來完全不同的地方。你一半的應用程式程式碼與資料變更有關。是時候讓你的 Web 框架尊重這一點了。
變更在 Next.js 中的運作方式:Next.js 在這裡不會為你做任何事。<button onClick={itsAllUpToYou}>
。通常,你會管理表單的狀態以了解要發布的內容、新增一個 API 路由來發布、自行追蹤載入和錯誤狀態、重新驗證資料並在 UI 中傳播變更,最後處理錯誤、中斷和競爭條件(但說實話,沒有人真的處理這些東西)。
變更在 Remix 中的運作方式:Remix 使用 HTML 表單。我知道你在想什麼。
噗... 我正在建構一個 Web 應用程式,這永遠行不通。
你可能會認為你即將看到的 API 無法滿足現代 Web 應用程式的需求。高度互動的 Web 應用程式一直是我的整個職業生涯,Remix 的設計考慮到了它們。僅僅因為這看起來像以前的 PHP,並不表示它不能擴展到現代、複雜的使用者體驗。我們喜歡說 Remix 可以向上擴展,但它也可以向下擴展。所以讓我們回到過去,以幫助你了解 Remix。
自網路誕生以來,變更被建模為一個表單和一個伺服器頁面來處理它。完全忽略 Remix,它看起來像這樣
<form method="post" action="/add-to-cart">
<input type="hidden" name="productId" value="123" />
<button>Add to Cart</button>
</form>
// on the server at `/add-to-cart`
export async function action(request) {
let formData = await request.formData();
return addToCart(formData);
}
瀏覽器使用 POST 將表單序列化資料導航到 "/add-to-cart"
、新增待處理的 UI,並在完成後使用來自你資料庫的所有新資料來呈現一個新頁面。
Remix 做的事情與 HTML 表單相同,只是使用大寫字母 F 的 <Form>
和你路由上的 action
函數(想像你的 Next.js 頁面是它們自己的 API 路由)進行最佳化。它使用 fetch
而不是文件重新載入來發布,然後重新驗證頁面上的所有資料與伺服器保持同步,以使 UI 與後端保持同步。這與你在 SPA 中習慣做的事情相同,只是 Remix 為你管理一切。
除了表單和伺服器端動作之外,不需要應用程式程式碼來與伺服器通訊變更。也沒有應用程式內容提供者或全域狀態管理技巧來將變更傳播到 UI 的其餘部分。這就是為什麼 Remix 套件比 Next.js 套件小將近 30% 的原因,你不需要所有這些程式碼來與你的「API 路由」對話。
喔,我又說謊了。該程式碼實際上在 Remix 中有效。如果你使用小寫字母 <form>
,瀏覽器會處理發布而不是 Remix。適用於 JavaScript 無法載入的情況 😅 (稍後會詳細說明)
你可以透過詢問 Remix 關於繁忙的旋轉符號和進度的轉換或要發布以建立樂觀 UI 的資料,來擴展到精美的 UI。該模型是 HTML 表單,而功能是你的設計師想出的任何內容。而且你不必完全重新架構你的實作來說「沒問題,我們可以做到。」
更小的套件和簡單的變更 API 並不是 Remix 在這裡為你做的唯一事情。
由於 Remix 會處理你與伺服器的所有互動(資料載入和資料變更),因此它在 Web 框架領域具有獨特的能力來修復 Web 應用程式長期存在的問題。
當「新增至購物車」後端處理常式擲回錯誤時會發生什麼事?在這裡,我們阻止對將項目新增至購物車的路由的請求,以查看會發生什麼事。
什麼也沒發生。錯誤處理很困難而且令人厭煩。許多開發人員只是像他們在這裡一樣跳過它。我們認為這是一個很糟糕的預設使用者體驗。
讓我們看看在 Remix 中會發生什麼事。
Remix 會處理你的應用程式中與資料和呈現相關的每個錯誤,即使是伺服器上的錯誤。
你只需在你應用程式的根目錄中定義一個錯誤邊界。你甚至可以更細緻地僅關閉頁面中發生錯誤的部分。
Remix 可以做到這一點而 Next.js 無法做到的唯一原因是 Remix 的資料抽象並未停止於如何將資料載入到你的應用程式中,而是也停止於如何變更它。
使用者常常會意外地按兩次按鈕,而大多數應用程式並不能很好地處理它。但有時你會看到一個你完全預期使用者會快速按一下的按鈕,並且希望 UI 立即回應。
在這個應用程式中,使用者可以變更購物車中項目的數量。他們很可能會快速按幾次以增加數量。
讓我們看看 Next.js 應用程式如何處理中斷
很難準確地看到發生了什麼事,但如果你拖動影片控制項,你可以看得更清楚。中間有一個從 5 到 6 再到 5 的奇怪事情。最後幾秒是最有趣的。你可以看到發送的最後一個請求到達(到 4),然後幾幀後發送的第一個請求到達!數量欄位從 5 跳到 4,再跳到 2,而沒有任何使用者互動。很難信任的 UI。
此程式碼未管理競爭條件、中斷或重新驗證,因此 UI 現在可能與伺服器不同步(這取決於 2 或 4 是否是最後一個到達伺服器端程式碼的)。管理中斷和在變更後重新驗證資料本可以防止這種情況發生。
我明白了,處理競爭條件和中斷很困難!這就是為什麼大多數應用程式不這樣做的原因。Vercel 團隊是業界最有才華的開發團隊之一,但他們甚至跳過了它。
事實上,當我們在上一篇部落格文章中移植 React Core 團隊建構的 React 伺服器元件範例時,他們也有同樣的錯誤。
我之前說過我們對網路索引標籤非常狂熱。讓我們看看 Remix 如何處理這個問題。
你可以看到 Remix 在中斷時取消請求,並在 POST 完成後重新驗證資料。這確保了整個頁面(而不僅僅是此表單)上的 UI 與你的表單剛對伺服器所做的任何變更保持同步。
你可能會認為也許我們只是比 Next.js 應用程式更注重細節。所有這些行為都不在應用程式程式碼中。它們都是內建於 Remix 的資料變更 API 中。(它實際上只是在做瀏覽器對 HTML 表單所做的事情...)
Remix 中客戶端和伺服器之間的無縫整合和轉換是前所未有的。
在我們數十年的 Web 開發職業生涯中,我們記得它曾經是多麼簡單。將按鈕放入表單中,將其指向寫入資料庫的頁面、重新導向、取得更新的 UI。它是如此簡單。
在設計 Remix API 時,我們總是首先考慮平台。就像變更工作流程一樣。我們知道 HTML 表單 API + 伺服器端處理常式是正確的,因此我們以此為基礎建構。這不是目標,但一個非常驚人的副作用是,慣用的 Remix 應用程式的核心功能可以在沒有 JavaScript 的情況下工作!
雖然以這種方式使用 Remix 完全有效,但我們的意圖並不是讓你建構沒有 JavaScript 的網站。我們對建構出色的使用者介面抱有很大的抱負,而你需要 JavaScript 來做到這一點。
我們不會說「Remix 在沒有 JavaScript 的情況下工作」,而是說「Remix 在 JavaScript 之前工作」。也許你的使用者在載入 JavaScript 時剛進入火車上的隧道。當他們再次出現時,頁面通常仍然可以運作。我們實際上只是為了追求 HTML 的簡單性,但我們最終得到了一個難以置信的彈性框架。
我們也尋求 Web 平台來撰寫你的伺服器端程式碼。Remix 沒有發明另一個新的 JavaScript 請求/回應 API,而是使用了 Web Fetch API。為了處理 URL 搜尋參數,我們使用內建的 URLSearchParams
。為了處理表單資料,我們使用內建的 FormData
。
export function loader({ request }) {
// request is a standard web fetch request
let url = new URL(request.url);
// remix doesn't do non-standard search param parsing,
// you use the built in URLSearchParams object
let query = url.searchParams.get("q");
}
export function action({ request }) {
// formData is part of the web fetch api
let formData = await request.formData();
}
你會發現,當你開始學習 Remix 時,你花在 MDN 文件上的時間,甚至可能比 Remix 文件還多。我們希望 Remix 能幫助你建構更好的網站,即使你沒有在使用它。
更擅長使用 Remix 的同時,也順便更了解網路技術。
這是我們的核心價值。雖然 Remix 應用程式速度非常快,但我們實際上並非過度關注效能,而是專注於提供優良的使用者和開發者體驗。我們從平台尋找問題的答案,讓它們更容易使用,效能通常就會自然而然地得到提升。
現在你了解這兩個框架如何運作,讓我們來看看應用程式如何回應變更。我一直很喜歡「為變更進行最佳化」這句話,我們在設計 Remix API 時也經常談到這一點。
假設你想變更首頁上的產品,那會是什麼情況?在 Next.js 中你有兩種選擇:
重新建構並重新部署你的應用程式。你的建構時間會隨著商店中的產品數量線性增長(每次建構都必須從 Shopify 為每個產品提取資料)。僅僅變更頁尾中的一個錯字,就需要你從 Shopify 下載每個產品才能部署該變更。當你的商店成長到數千種產品時,這將成為一個問題。
使用 增量靜態再生。Vercel 意識到 SSG 建構時間的問題,因此他們創建了 ISR。當請求頁面時,伺服器會發送快取版本,然後在背景中用新資料重新建構它。下一個訪問者會獲得新快取的版本。
如果頁面在部署時沒有建構,Next.js 將會伺服器渲染頁面,然後將其快取在 CDN 上。這完全是 HTTP 的 stale-while-revalidate 機制所做的事情,只不過 ISR 帶有非標準的 API 和供應商鎖定。
在 Remix 中,你只需在 Shopify 上更新產品,你的產品就會在快取 TTL 時間內更新。你也可以在下午設定一個 Webhook 來使首頁查詢失效。
這個基礎設施比使用 SSG 需要更多的工作,但正如我們在本文中所看到的,它可以擴展到任何規模的產品目錄、任何種類的 UI(例如搜尋頁面),而且實際上隨著使用者增加,它比 SSG 更快(我們可以快取常見的搜尋查詢)。你也不會被綁定到特定的主機,而且幾乎沒有被綁定到框架,因為 Remix 主要使用標準的 Web API 來處理應用程式邏輯。
此外,我們認為僅以一種方式在伺服器上載入資料,可以帶來更清晰的抽象。
這是個好問題。只有當你的網站有流量時,伺服器和 HTTP 快取才會運作。事實證明,只有當你的網站有流量時,你的業務才會運作😳。你不需要每天兩個頁面瀏覽量來加快一秒,你需要的是郵件列表。
如果快取未命中的請求佔據你訪問量的很大一部分,那麼獲得 100% 的快取命中率也無法解決你的業務問題:你不是有技術問題,而是有行銷問題。
讓我們看看另一個變更。想像一下產品團隊來找你,說要變更首頁以顯示與使用者過去購買過的產品相似的產品,而不是一組固定的產品。
就像搜尋頁面一樣,SSG 已經無法使用,你的效能也會因此下降。SSG 的使用案例真的有限。
幾乎每個網站都有使用者。隨著你的網站成長,你將開始向使用者顯示越來越多個人化的資訊。每次都會變成客戶端提取。在某個時間點,你頁面上的大部分內容都是客戶端提取的,而你的效能也就消失了。
對於 Remix 而言,這只是後端不同的資料庫查詢。
考慮一下電子商務食物鏈的頂端:Amazon.com。整個頁面都是個人化的。我們從一開始就已經知道結果。投資於可以帶你到達那裡的架構,而不是當產品團隊調整首頁時你需要捨棄的東西。
很容易忽略 Remix 表面上簡單的 <Form>
+ action
+ loader
API 以及盡可能將所有內容保留在伺服器上的設計的強大功能。它改變了遊戲規則。這些 API 是 Remix 更快的頁面載入、更快的轉換、更好的變更 UX(中斷、競爭條件、錯誤)以及更簡單的開發者程式碼的來源。
Remix 應用程式的速度來自後端基礎設施和預取。Next.js 的速度來自 SSG。由於 SSG 的使用案例有限,尤其是在功能和資料擴展時,你將會失去這種速度。
SSG 和 Jamstack 是解決後端服務緩慢問題的絕佳解決方案。最新一代的平台和資料庫速度很快,而且只會越來越快。即使是支援這些應用程式的 Shopify API,也可以在 200 毫秒內從世界上幾乎任何地方向查詢發送回應,我從除南極洲以外的每個大陸都測試過了!(這個月當 @chancethedev 在那裡時,需要他幫我試試看。)
老實說,完全可以跳過本文討論的所有快取策略,並在伺服器上的每個請求中訪問 Shopify API。載入時間不是 1.2 秒,而是 1.4 秒。不是 0.8 秒,而是 1 秒。沒有任何影響。如果你的後端 API 速度很慢,請花時間加快你的後端速度。如果你無法控制它,請部署你自己的伺服器並在那裡進行快取,你可以在那裡加快所有使用者的任何頁面速度。
投資於你的後端將會產生與 SSG 相同的效能結果,但它可以擴展到任何種類的頁面。它會比 SSG 需要更多初始工作,但我們認為為了你的使用者和你的程式碼,從長遠來看這是值得的。
資料載入只是故事的一半。在 Remix 中,你的資料抽象也可以封裝資料變更。所有程式碼都保留在伺服器上,從而產生更好的應用程式程式碼和更小的瀏覽器套件。
使用 Next.js,你必須將你自己的資料變更程式碼發送到瀏覽器,以便與 API 路由互動並將更新傳播到其餘的 UI。正如我們在本文中所看到的,即使是頂尖的團隊也會在錯誤、中斷和競爭條件方面搞砸。
你不是忽略了
getServerSideProps
嗎?
有些人說你可以用 getServerSideProps
來完成 Remix 所做的一切。這個問題來自於我們沒有機會很好地解釋 Remix!
如前所述,這絕對會加快搜尋頁面的速度。然而,你仍然必須處理資料變更。你需要結合使用 getServerSideProps
、API 路由和你自己的瀏覽器程式碼,這些程式碼與它們進行變更(包括錯誤處理、中斷、競爭條件、重新導向和重新驗證)。我們在這裡真正想說的是「你可以建立你自己的 Remix」。確實,你可以。我們已經這樣做了😇。
呼!
現在我們已經回答了大家一直在問我們的大問題,我們未來的文章將會真正開始展示 Remix 可以做的事情!