Hi,我是 ssh,今天给大家分享一篇关于 Next.js 的吐槽。作为一个想要产品化的框架,提供一致的 API 和易用性是必不可缺的。但是 Next 不知道是傲慢还是疏忽,让框架开发者 Pilcrow 饱受其苦,下面是他的这篇 Next.js, just why?
我真不想抱怨。但是在使用过的所有框架中,Next.js 一直是非常让我头疼的。而且这几个月的情况一点都没好转。
在 Lucia 中,Auth.handleRequest()是一个方法,它会创建一个新的 AuthRequest 实例,这个实例包含了一个 AuthRequest.validate()方法。这个方法会检查请求是否来自可信的源(CSRF 保护),验证 session cookie,并在需要的时候设置一个新的 cookie(这是可选的)。至少,这个方法需要请求的 URL 或主机,请求方法和请求头等信息。大多数(如果不是全部的话)JS 框架(比如 Express、SvelteKit、Astro、Nuxt 等)都会提供某种请求对象,通常是一个 Request 对象或 IncomingMessage 对象,所以这本不该是个问题。
然而 Next.js 就是个例外。
Next.js 12
Next.js 12 和页面路由没什么大问题。在 getServerSideProps()中你可以访问 IncomingMessage 和 OutgoingMessage 对象,这样你可以在服务器端渲染页面前,在服务端运行一些代码。
代码语言:javascript复制export const getServerSideProps = async (
req: IncomingMessage,
res: OutgoingMessage,
) => {
req.headers.cookie; // 读取header
res.setHeader('Set-Cookie', cookie.serialize()); // 设置cookie
return {};
};
但是它也确实存在一些问题。第一个是,当你把页面部署到 Edge 的时候,你就没法设置 cookie 了。我不太清楚 Next.js 的历史,但是在我看来,它的 API 设计得不太合理。另一个问题是中间件使用的是标准的 Request 对象。Next.js 团队转向使用 web 标准是值得称赞的,但我认为这只会使情况变得更糟,因为 API 不一致(IncomingMessage 和 Request)。但说到底,它勉强可以工作...
Next.js 13
说 Next.js 已产品化简直是个笑话。
Next.js 13 引入了新的路由 - 应用路由(App Router)。其中的所有组件默认都是 React 服务器组件,所以会一直运行在服务器端。所有内容都会在服务器端渲染,然后作为纯 HTML 发给客户端。
代码语言:javascript复制// app/page.tsx
const Page = async () => {
console.log('我一直运行在服务器上');
return <h1>Hello world!</h1>;
};
如果你用过 Remix、SvelteKit 或 Astro,它类似于 loader 模式。如果你用过 Express 或类 Express 的库,它就像 app.get("/", handler)。所以你会以为请求或请求上下文会作为参数传递给这个函数...是吧?根本不是!
代码语言:javascript复制// app/page.tsx
const Page = async request => {
console.log(request);
return <h1>Hello world!</h1>;
};
不一致的 API
那么,怎样才能在页面里获取请求呢?问题是,你没法获取!没错,什么天才的主意啊!它大力推广服务端的使用,却不允许用户访问请求对象。
其实也不是完全不可以,但是很麻烦。它提供了 cookies()和 headers()方法,但你需要特别导入它们。
代码语言:javascript复制// app/page.tsx
import { cookies, headers } from 'next/headers';
const Page = async () => {
cookies().get('session');
headers().get('Origin');
return <h1>Hello world!</h1>;
};
好吧,也许它们有正当理由不直接把请求作为参数传进来。但是为什么只提供访问 cookie 和 header 的 API 呢?为什么不导出一个 request()方法,它返回一个 Request 对象或请求上下文?这变得更让人困惑的是,API 路由处理程序和中间件可以访问 Request 对象。
代码语言:javascript复制// app/api/index.ts
export const GET = async (request: Request) => {
// ...
};
这里是更有趣的部分。你无法在中间件(middleware.ts)中使用 cookies()和 headers()!
请给我们一个统一的 API 来和请求对象交互。
随意的限制
还记得在 Edge 环境下你无法在 getServerSideProps()中设置 cookie 吗?好吧,使用应用路由器你甚至在任何时候渲染页面时都没法设置 cookie,即使是在 Node.js 环境下。等等,我们为什么不能使用 cookies()方法呢?
代码语言:javascript复制// app/page.tsx
import { cookies } from 'next/headers';
const Page = async () => {
cookies().set('cookie1', 'foo');
return <h1>Hello world!</h1>;
};
它暴露了 set()方法,但当你试图这样做时,会报错!为什么呢?我想不出任何合理的解释来证明这个限制是必要的。SvelteKit 可以很好地实现这一功能。每个 HTTP 框架都可以做得很好。就连 Astro 这个关注静态网站生成的框架,在 1.0 版本之前也能很好地实现这一功能。
而且,与 cookies()方法不同,后者可以在 API 路由里设置 cookie,headers()方法总是只读的。这又是一个不一致的地方。
我最后一个抱怨的是中间件。为什么它总是运行在 Edge 上呢?为什么要限制它不允许运行数据库查询或使用 Node.js 模块呢?这只会使一切变得复杂,也使得在中间件和路由之间传递状态变得不可能——Express、SvelteKit 和 Astro 其实都可以实现这一功能。
为什么要这样设计?
所有这些小问题积累起来,作为一个库的作者,支持 Next.js 很困难,有时候几乎是不可能的。缓慢的启动和编译时间,以及容易出 Bug 的开发服务器,都让使用 Next.js 整体上不是很愉快。我还没有提缓存,这是另一个让人头疼的问题。
我不想对 Next.js 团队或 Vercel 有任何恶意揣测,但是他们似乎直接无视了在 page.tsx 中设置 cookie 的问题。他们的开发者关系人员在 GitHub 和 Twitter 上的回应还不错,但对此并没有任何回应。他们的开发者关系人员甚至 CEO 都联系过我,问我有没有任何可以改进的地方,我提到了 cookie 问题,但没有任何回应。我在 Twitter 上也@过他们多次。我不是指望他们立刻做出改变,但是一些确认还是很好的。
我理解开源项目不该有太高期望。我自己也是一个库的作者。但是来吧。这是一个由大公司支持的大型框架。有一些期望真的很过分吗?
我认为其根本原因有两点。首先,发布太匆忙。文档还很不完善,一切似乎都不是很成熟。其次,是 React 本身,特别是服务器组件的问题。React 仍然想要像一个库一样,但它显然已经是一个框架了。Next.js API 和 React API 在服务器端职责上的重叠混乱不堪。React 需要接受一个统一的框架,不管是他们自己的还是 Next.js,然后全力以赴。