Next.js 越來越難用了

作者 | propelauth
譯者 | sambodhi
策劃 | tina
導讀:在選擇下一個項目的框架時,深入了解各選項的優缺點至關重要。儘管 next.js 強烈推薦使用 app router(應用路由器),但我們也必須認識到,它仍然存在着諸多不足和複雜性,這可能使其並不適用於所有項目。相比之下,react 官方文檔仍持續推薦 pages router(頁面路由器),並將 app router 視為前沿技術。本文將深入剖析這兩者的差異,並為不同用例下如何選擇合適的工具提供建議,助你在複雜性與功能性之間找到完美的平衡點。  

最近,我撰寫了一篇博客文章,深入探討了 next.js 的中間件在應對服務器組件的某些限制方面的作用。這引起了廣泛討論,大家紛紛探討這種方法是否切實可行,以及 next.js 的開發體驗是否真的不盡如人意。

在我看來,next.js 的 app router 存在兩大主要問題,導致其難以被廣泛應用:

  • 你需要深入了解其內部機制,才能完成看似簡單的任務。

  • 其中存在諸多潛在的陷阱,而且這些陷阱默認存在,並非需要用戶主動選擇才會遇到。

為了更好地理解這些問題,我們可以回顧一下它的前身——pages router。

為什麼選擇 next.js 
而不是 create react app

當我首次接觸 next.js 時,它當時的「競爭對手」是 create react app(簡稱 cra)。當時我所有的項目都是基於 cra 來開發的,但之後我選擇轉向 next.js,主要有兩大原因:

  • 我偏愛基於文件的路由方式,因為它讓我能夠減少樣板代碼的編寫。

  • 每次啟動開發服務器時,cra 都會自動打開 http://localhost:3000 頁面(這種做法很快就讓我感到不便),而 next.js 則沒有這樣的「貼心」設計。

第二個原因或許顯得有些滑稽默,但對我而言,它確實表明了 next.js:提供了更優秀的 react 默認設置

這正是我所追求的。直到後來,我才發現 next.js 還有更多功能。api 路由非常吸引我,因為它們無需額外的基礎設施配置就能提供無服務器函數,這對於像營銷網站的「聯繫我們」表單這樣的功能來說非常便利。getserversideprops允許我在頁面加載前在服務器端運行基礎函數。

這些概念不僅功能強大,而且操作起來也十分簡單。

api 路由與其他路由處理程序在外觀和運作方式上都很相似。如果你曾使用過 express 或 cloudflare workers,那麼你只需瀏覽一下路由處理程序,就能發現其中許多概念都是相通的。至於 getserversideprops,儘管它有些特別,但一旦你掌握了獲取 request 和響應格式的方法,就會發現它也相當容易上手。

next.js 13 的新功能:
app router 發佈

next.js 13 版本發佈了 app router,帶來了眾多新功能。其中,server components 的引入使得 react 組件可以在服務器端進行渲染,從而減少了需要發送給客戶端的數據量。

此外,新版本還引入了 layouts 功能,允許開發者定義多個路由共享的 ui 元素,並在每次導航時無需重新渲染,從而提高了頁面加載效率。

然而,在緩存方面,新版本卻變得更加……複雜。

儘管這些新功能十分有趣,但最大的損失在於簡單性的減少。

當框架未按預期工作時

作為開發者,我們都曾有過這樣的經歷:面對代碼難題時,往往會感到困惑並大聲問道:「為什麼這不起作用?」

這種體驗每個人都曾有過,而且總是讓人沮喪。對我來說,如果問題並非源於代碼本身的 bug,而是源於對事物工作原理的誤解,那就會更加令人頭疼。

此時,你不再只是疑惑:「為什麼這不起作用?」而是開始思考:「為什麼它這樣工作……而不是那樣?」

不幸的是,app router 就充滿了這樣的微妙之處。

讓我們回到我的最初問題:我僅僅希望在服務器組件中獲取 url。關於這個主題,github 上有一個非常熱門的問題的解答,我將在這裡分享部分內容:

當我們深入思考時,問題「為什麼我無法訪問 pathname 或當前 url?」其實只是冰山一角,其背後隱藏着更大的疑問:「為什麼我無法直接訪問完整的請求和響應對象?」

next.js 作為一個既能靜態也能動態渲染的框架,它巧妙地將工作劃分為多個路由段。儘管直接暴露請求 / 響應對象能帶來極大的靈活性,但這些對象本質上具有 動態性,它們會影響整個路由的處理。這種設計限制了框架在當前(如緩存和流式傳輸)以及未來(如部分預渲染)優化方面的能力。

為了解決這一問題,我們曾考慮過直接暴露請求對象並追蹤其訪問位置(比如使用代理)。但這樣的做法會使我們難以追蹤這些方法在代碼庫中的使用方式,並可能導致開發者在不經意間選擇了動態渲染。

因此,我們採取了另一種策略,即暴露 web 請求 api 中的特定方法,並針對不同的使用場景進行了統一和優化:這些 api 覆蓋了組件、服務器操作、路由處理程序和中間件等場景。通過這些 api,開發者可以明確選擇框架的啟發式方法,如動態渲染,同時也讓 next.js 更容易追蹤使用情況,分解工作並儘可能優化性能。

舉例來說,當使用 headers 時,框架會選擇動態渲染來處理請求。而在處理 cookies 時,你可以在 react 渲染上下文中讀取 cookies,但只能在變更上下文中(如服務器操作和路由處理程序)設置 cookies,因為一旦開始流式傳輸,就無法再設置 cookies 了。

這個回答確實非常出色。它不僅寫得清晰易懂,而且幫助我對一些底層問題有了更深入的理解,更讓我認識到了不同方法之間的權衡,這些我之前完全沒有思考過。

然而,話雖如此,如果你是一名開發人員,只是希望在服務器組件中獲取 url,那麼在閱讀完這篇回答後,你可能還需要進一步查詢五個相關問題,最後才會意識到可能需要重新構建或調整你的代碼結構。

這篇文章很好地總結了我對此的感受:

這並不意味着它一定是錯誤的——而是有些出乎意料。

那篇原始文章還提到了一些其他微妙的細節。其中一個常見問題涉及處理 cookies 的方式。你可以在任何地方調用cookies().set("key", "value"),儘管這能通過類型檢查,但在某些情況下,運行時可能會出錯。

與「舊」方法相比,那時我們可以輕鬆獲取一個完整的request對象,並在服務器上隨心所欲地操作,現在的複雜性確實有所增加。

我還要指出的是,「默認開啟」的激進緩存策略帶來了糟糕的體驗。我認為,大多數人更希望自主選擇是否使用緩存,而不是在大量文檔中苦苦尋找如何關閉它。

在 propelauth,我們經常收到的錯誤報告並非真正的錯誤,而是用戶誤以為自己發起了一個 api 調用,但實際上只是讀取了緩存的結果。

所有這些都引出了一個問題:這些特性和優化究竟是為了誰而設計的呢?

設計全能產品十分挑戰

我所描述的這些過於複雜的特性對一些人來說確實具有重要意義。比如,如果你正在構建一個電子商務平台,這裡提供的某些功能就十分出色。

這些功能可以顯著提升頁面加載速度。因為發送給客戶端的數據量減少了,頁面加載速度得以加快;由於積極的緩存策略,頁面加載速度也得以提升;並且,當用戶導航到新頁面時,只有頁面的部分內容需要重新渲染,這也進一步加快了加載速度。在電子商務領域,頁面加載速度的提升意味着更多的收入,因此,為了獲得這些優勢,你完全會接受使用更為複雜的框架。

然而,如果我是在為我的 saas 應用程序構建儀錶板,我可能就不會太關心這些功能了。我更注重的是新功能發佈的速度,而所有這些複雜性對我的開發團隊來說反而成了負擔。

我個人對 app router 的體驗和挫折與其他人有所不同,因為我們擁有不同的產品、不同的用例和不同的資源。尤其作為一個長時間投入於編寫並幫助他人編寫 b2b saas 應用程序的人,我認為使用 app router 的開發體驗遠不如 pages router。

隨着框架的發展,
這是不可避免的嗎?

隨着產品 / 框架的不斷發展,它們往往會變得更為複雜。客戶的需求會不斷增加,大客戶更是會提出更為具體的要求。由於大客戶支付更多的費用,因此你會優先考慮並構建這些更為具體的功能。

然而,那些曾經喜歡一切簡單的客戶可能會對不斷增加的複雜性感到困擾,然後……瞧,一個全新的框架誕生了,它看起來簡單多了。這時,人們會開始呼籲:我們都應該轉移到那個新框架上去!

要避免這種局面並不容易,但緩解的一個有效方法是,不要強求所有人都去應對只有部分人需要的複雜性。

推薦的東西並不一定適合你

app router 面臨的一個主要問題是:

next.js 在 app router 尚未真正準備好投入生產使用之前就正式推薦了它。next.js 並未就 typescript、eslint 或 tailwind 是否適合你的項目給出明確建議(儘管在 typescript 和 eslint 上默認選擇了「是」,tailwind 則選擇了「否」—— 抱歉,tailwind 的粉絲們),但 next.js 堅定地認為你應該使用 app router。

然而,react 官方文檔卻持有不同觀點。它們目前推薦的是 pages router,並將 app router 描述為「前沿的 react 框架」。

從這個角度來看待 app router 會更有意義。與其將其視為 react 的推薦默認選項,不如將其視為一個 beta 版本。它的體驗相對複雜,一些原本簡單的事情現在變得困難 / 不可能,但這正是「前沿」技術所預期的情況。

因此,當你為下一個項目選擇框架時,請注意 app router 仍存在許多不足。你可能會更容易找到一個更適合你用例的不同工具。

作者簡介:

propelauth 提供端到端的 b2b 產品管理身份驗證服務。

https://medium.com/@propelauth/its-not-just-you-next-js-is-getting-harder-to-use-5ab30a24282a

聲明:本文為 infoq 翻譯整理,未經許可禁止轉載。