9 月末,Next.js 12.3 發布。本文不是要介紹其中的酷炫新特性,而更多的是關於用 Next.js 來構建企業級 JavaScript 應用程序時不時冒出的一些奇異觀點。
技術有兩個方面。一方面,只要你能構建真正解決用戶問題且價格合理的解決方案,技術就無關緊要。例如,Python 和 Ruby 之間的選擇,以及用 Java 或 Kotlin 來構建你的 Web 應用程序。我們稱之為您或您的團隊選擇生態系統的偏好。
然而,另一方面更有趣——為正確的工作選擇正確的工具。這就是會導致困惑的地方。沒有人是無所不知的。即使我們試圖進行客觀分析時,也是基於主觀經驗的。這正是要討論 Next.js 的地方。圍繞 Next.js 的好處的宣傳模糊了現實情況。
毫無疑問,Next.js 是一個用來構建基於 React 的應用程序的非常棒的框架(控制反轉)。而且,它也完全稱得上是最好的框架,因為當什麼都沒有時,它是第一個針對 React 的真正框架。在此之前存在的替代方案——create-react-app 只是一個簡單的腳手架工具附帶一個 CLI。2022 年,我們有很多選擇,例如 Fusion.js、Electrode、Remix 等等。
有很多非常棒的文檔介紹了 Next.js 提供的最佳內容。但它們都沒有回答這個基本問題——Next.js 是適合你的工作的正確工具嗎?但在回答這個問題之前,我們先來談一談 Next.js 的缺點。
DX——開發人員體驗是一回事,而 AX——架構體驗是另一回事。Next.js 在前者很棒,但在後者很糟糕。關於軟件架構,有一些古老的黃金法則根本不適合 Next 生態系統。
由於 SSR 功能,Next 的設計選擇非常特立獨行。這在 Next 為應用程序強加的架構類型中是顯而易見的。例如:
基於文件的路由減少了應用程序的可組合性。例如,我不能將幾個頁面作為單獨的 NPM 包由不同的團隊開發。可組合性是大型應用程序必須具備的。
在 Next 中雖然也有一些方法可以進行組合,但是沒有比較整潔的方法。在構建 Web 應用程序時,路由本質上應該是聲明式的,這是一個由來已久的教訓。
Next 12.2 大大削減了 middleware(中間件)的功能,來使其適配 Vercel 的 serverless/ 邊緣計算模型。你不能再從 middleware 返迴響應體。這對於框架來說,不是一個好的特性。框架是一種抽象,它應該隱藏複雜性,而不是完全扼殺一個特性。
Next API 存在的唯一原因是用來編寫 BFF——Backend for Fronted,因為你想要處理服務器端秘鑰。好的 API 服務器允許我們在應用程序上下文(application context)或請求上下文(request context)中維護狀態。Next 中唯一維護狀態的方法是有狀態的 ESM 模塊,我們知道真正的單例很難維護,特別是當我們有 bundlers 位於兩者之間時。而且,模擬靜態 ESM 導入並不容易。上下文對象是必要的,因為它們通過支持依賴注入模式和提供各種應用程序狀態對象的延遲或緊急初始化來簡化測試。
在我個人看來,Next 是一個被設計成專門用於邊緣平台或 serverless 平台的框架。
度來看,有 4 類應用程序:
單體應用程序
迷你應用程序
微型應用程序
基於插件的應用程序(微前端)
這些類別因應用程序的規模、生命周期和運行時上下文而異;這個話題值得另外再寫一篇文章。Next 被設計用來構建單體前端應用程序。在許多組織中,將一個大型應用程序拆分為多個比較小的應用程序是解決擴展性和複雜性問題的最簡單的方法。這可以通過一個簡單的 Node.js 服務器服務多個前端應用程序來實現。
如果我們決定採用 Next.js 技術路線,最終的結果是我們會創建一個完全單體的應用程序。之後沒有簡單的遷移路徑。每個應用程序都部署在一個子域名或完全不同的域名。所有其他問題,例如訪問控制、session 管理、跟蹤等等都會變得複雜起來。
要讓所有應用程序通過不同的路徑在同一個域名運行,在 Next 中,唯一直接可用的方法是使用 Zones。Zone 不是一個 API,而是一個僅在 Vercel 上支持的部署概念。如果您沒有使用 Vercel,那麼您必須構建自己的系統來實現反向代理或類似網關來映射傳入的 URL。
除非我們犧牲 Next 的許多現成功能並添加一些神奇的構造,否則我們永遠無法將它打包成一個庫,畢竟,它是一個框架和構建工具的集合體。
這在 StackOverflow 上仍是一個開放的問題:
鏈接:
https://stackoverflow.com/questions/71752233/how-to-package-next-js-application-as-a-reusable-node-library
我們實現這點的唯一方法是使用一個自定義的使用 Koa.js 的 Node.js 服務器,Koa.js 會負責處理這些事情並能夠將它作為庫發布:
即使我們可以將它作為一個庫提供,模本文件的數量也會大而繁多。另一種替代方法是 CLI 或一些模板解決方案,由於需要在所有應用程序中保持更新,因此這些解決方案並不實用。
從構建到部署,Next 應用程序在前端活動之間緊密耦合。應用程序如何在服務器和客戶端運行也存在耦合。這意味着我們將自己鎖在這個框架中。幾乎不可能部分地對系統進行組裝 / 升級 / 實驗。如果應用程序的生命周期比較長,那麼這是一種不可接受的架構選擇。
具體來說,每個 Next 應用程序有三個控制周期:
服務器周期
客戶端周期
構建周期
這三個周期是緊密耦合的。從那時起,這種反模式突然變得可以接受了!雖然它確實改善了構建 Web 應用程序的開發體驗,但長遠來看,顯式解耦的方案更值得考慮。
如果可能,軟件應該是組合的,而不是構建的,只需要添加代碼將現有部分粘合起來。這種組合應該受我們的控制。而 Next 使我們無法控制。就像我們不買零件不能更換的汽車一樣,一種技術如果不能更換部分組件就不應該被採用。
Webpack 是 Next.js 使用的編譯器。問題是,不用 Webpack 就無法使用 Next。一個長壽的軟件需要好的架構,這個架構應該使我們能夠部分地或整體地升級。能夠獨立工作的抽象是必須的。
整潔的應用程序架構應該始終遵循某種形式的良好定義的模式。它可以是傳統的分層架構(應用程序的第 n 層應該只與 n+1 層對話,並被 n-1 層調用),也可以是六角形 / 整潔 / 洋蔥架構(反向依賴關係)。而且它更適合用於編寫 API 驅動的服務。
這在 Reddit、Twitter、Hacker News 等社交媒體上得到了進一步的推波助瀾,像這樣的爭論帖子還有很多。
鏈接:
https://www.reddit.com/r/nextjs/comments/esj0l8/is_it_recommended_to_use_nextjs_for_a_fullstack/
可以肯定的是,Next 並沒遵循這條設計原則。
使用 Next 構建應用程序時,很難遵循 12 因素方法。
鏈接:
https://stackoverflow.com/questions/70565191/how-to-enable-12-factor-application-bundling-with-next-js
Next 支持 4 種類型的渲染:
服務器端渲染(SSR)
靜態站點生成(SSG)
客戶端渲染(CSR)
增量靜態生成(ISG)
不要介意 CSR 不是默認的或被視為一等公民!為了通用,Next 中有些東西分得並不那麼明顯。處理環境就是一個典型的例子。根據 Next 文檔:
實際上,環境變量是構建時環境變量,而運行時配置是大多數應用程序在使用 SSR 或 CSR 時需要的配置。而且,消息傳遞也是錯誤的:
Next.js 優先事項
問題不在於是否支持某些東西,而在於架構體驗是否合理!目前看來,並非如此!
理想的部署模型確保了正確的軟件配置管理,並且默認構建一次可以隨處部署。
我並不熱衷於狀態同構。這簡直是假的。我們還沒有做到狀態同構。它太複雜了,而且很難解釋清楚:
正如 Miško Hevery 所說,這種一種棘手的變通方法。我不會詳細解釋為什麼同構好或者不好!這是一個單獨的話題。我所關心的是架構層面:
使用 SSR 並非易事。你要使用的任何庫都要和 SSR 兼容,或者你必須確保那些代碼只在瀏覽器端運行。你不能為你的庫簡單地使用一個閉包狀態(不建議庫有狀態)。當你有多個團隊單獨工作時,這個問題會迅速升級。
在代碼級別,某些事情是不可能的。例如,我不能使用某些動態條件處理靜態文件。我可以使用 API 路由,但這與其說是一種解決方案,不如說是一種折中方案。
再次說明,本文不是為了抱怨 Next,而是為了確定正確的用例。開箱即用,Next 使得許多事情變得簡單:
配置得相當好的 Webpack 編譯器
優化——靜態 HTML 頁面、圖像優化、polyfills 等。
默認路由
預配置的服務器——壓縮、緩存等。
完備的渲染策略
但是大多數企業應用程序(如下所述)都有一定程度的定製。當我們在使用 Next 時進行這些更改或者脫離它所提供的功能時,我們就會遇到問題。
與其為邊緣案例尋找解決方案,不如編寫我們自己的網站配置,啟動服務器,構建一個 React 應用程序。
比如,網站和 Web 應用程序。網站通常是靜態的,內容豐富。具有 SSG 和 ISR 渲染的 Next.js 絕對是一個理想的選擇。它比基於 Gatsby 的網站簡單地多,並且與其它靜態網站構建器相比,它通過 React 提供了更好的組件模型。
至於 Web 應用程序,面向 SEO 的應用程序(例如電子商務應用程序)和社交軟件應用程序(例如 Twitter)都非常值得用 Next.js 或基於 SSR 的解決方案來構建。
我們普通開發者開發的絕大多數應用程序都不是 Twitter 或亞馬遜。我們構建的都是通過 B2B 或 B2C 渠道向小微企業銷售的企業軟件。這樣的 SaaS 軟件通常需要身份驗證,並不怎麼需要 SEO。我稱之為企業軟件。
我們的日常任務管理器、項目管理工具、筆記應用程序、電子表格、記賬工具、人力資源工具都是這類軟件。
儘管客戶端渲染存在首屏渲染的性能問題,但它仍然是解耦的和比較簡單的架構模型。服務器端和客戶端之間的狀態是無聯繫的。通過配合使用 Service Workers、離線體驗、CDN、簡單的身份驗證服務器和 HTTP2,客戶端渲染能夠提供驚人的性能。
客戶端渲染仍然很棒。不要被你看到的東西愚弄了!
作為一個企業應用程序框架,Next 不是 Django 或者 Rails。你要做的就是更好地思考決策:
一個用來選擇適當的前端渲染方案的簡單決策樹
Next 團隊一直主張將其作為一個全棧框架,但並未真正提供良好的上下文,例如:
Next.js 作為一個全棧框架
很難說 Next 的發展方向,但它在預期用例之外的更多使用可能會導致它以自己獨特的方式支持所需的模式,從而朝着更加特立獨行的方向發展。
毫無疑問,使用 Next 將使事情變得複雜。新的 Layout RFC 相當複雜:
而且,核心團隊的回答並不完全令人信服,至少對我來說是這樣!
在我個人看來,Next 應該進一步研究可組合性,而不是試圖成為 Node.js 世界的 Django。最大的工程價值在於使 SSR 渲染作為一個簡單的庫而不是一個成熟的框架。用傳奇人物喬·阿姆斯特朗(Joe Armstrong)的話來說,Next 感覺就像一個「香蕉」。
50 年以前,一個無法定義良好應用程序邊界的框架應該是一個糟糕的設計!今天依然如此。Next.js 只是放大了糟糕的方面。也許,Next.js 的創造者並沒有打算讓用戶在其適用範圍之外使用它。但很難控制其適用性,主要是因為不了解領域或確切的業務需求。
簡單性不是敵人。它是你能擁有的最好的朋友。如果你的應用程序需要先登錄再訪問,而且對於邊緣渲染、極致的 Web 性能敏感性沒有要求,那麼客戶端渲染或者使用漸進式 JavaScript 的老派服務端渲染網站可能是更好的選擇。
儘管我們是在 Next.js 的上下文中討論服務器端渲染,但大多數爭論也適用於提供這種通用渲染的其它框架。
再次提醒你,Next 是一個令人驚嘆的框架,但你應該使用的用例有限。正如埃里克·雷蒙德(Eric Raymond)所言,Next 正在經歷面向對象編程(OOP)繁榮:
由於其簡單性,本文針對非公開面向 SEO 的應用程序介紹了企業軟件並推薦客戶端渲染(CSR)。但正確使用客戶端渲染是一門藝術。我們將在後續的文章中討論這個話題。
感謝 Charushila Patil 提供的精彩插圖和無數分享 Next.js 使用經驗的人們。
專注於用戶界面、狂熱功能、寫作並沉迷於可讀代碼,熱愛機器學習和 LISP..... 但每天都在寫 JavaScript。
原文鏈接:https://blog.webf.zone/you-dont-need-next-js-and-ssr-7c6bd27e78d8
聲明:本文為InfoQ翻譯,未經許可禁止轉載。
活動推薦

React:我愛你,但是你越來越讓我失望了
基於Deno的新交互框架:Fresh如何改善用戶體驗
跨過四個時代,JavaScript框架終於可以與原生應用SDK競爭了