我们被问到最多的问题莫过于
Remix 和 Next.js 有什么不同?
看来我们必须回答这个问题了!我们想直接且不带任何戏剧性色彩地回答这个问题。如果你是 Remix 的粉丝,并且想开始在推特上对这篇文章发表沾沾自喜的评论,我们恳请你在点击推特按钮前收起你的得意 🤗。水涨众船高。早在 Vercel 成立之前,我们就和 Vercel 的朋友们是朋友了。他们做得很棒,我们尊重他们所做的工作!
但请不要误会,我们认为 Remix 比 Next.js 有更好的权衡方案。(否则我们也不会构建它...)
我们鼓励你阅读整篇文章。这个讨论中有很多细微之处,是那些闪亮的图表和动画所无法捕捉的。希望到最后,你会考虑在你的下一个项目中使用 Remix(绝无双关 😂)。
我们认为比较框架最公平的方法是使用 Vercel 团队自己编写的 Next.js 示例应用程序。由于是他们编写的,他们所做的决定应该反映他们希望你如何构建你的应用程序。它也应该展示 Vercel 团队最引以为傲的功能。
我们移植了 Next.js 示例页面中的电商示例。它有一些我们喜欢的真实世界功能,似乎是他们投入最多精力的地方。
我们实际上构建了两个版本
请注意,这个应用程序没有利用我们认为 Remix 很酷的所有功能(比如嵌套路由!)。一旦我们回答了这个问题,我们就可以继续只谈论 Remix,敬请期待!
此外,我们在发布之前与 Vercel 分享了这篇文章。结果发现他们的示例运行在较旧版本的 Next.js 上,他们更新了它,所以我们花时间重新编写了这篇文章,以便与他们最新的示例进行比较。
我们认为他们是朋友,甚至是合作伙伴,因为 Vercel 是 Remix 的一个出色的部署目标。我几乎将 Remix 应用程序部署到了我听说过的所有托管服务上。Vercel 的开发者体验很容易成为我最喜欢的。“开发、预览、发布”的口号产生了真正的效果。就在今天早上,@gt_codes 和我试图找出生产中的一个错误,每个预览部署都有一个小截图,这帮助我们在几秒钟内找到了错误的提交。这是好东西。
现在这是一种有趣的关系,因为我们不仅是朋友和技术合作伙伴,我们还是框架竞争对手!所以,对于我们的朋友、合作伙伴和竞争对手 Vercel,Lee 将这篇文章背后的动机做了精彩的总结
当 DevTools 中存在竞争时,开发者会获胜
— Lee Robinson (@leeerob) 2021年11月30日
◆ Svelte 正在推动 React
◆ Remix 正在推动 Next.js
◆ Prisma 正在推动 ORM
◆ Deno 正在推动 Node.js
◆ Supabase 正在推动 Firebase
◆ esbuild / SWC 正在推动 JS 工具
◆ Bun 正在推动 SWC
还有什么?
请在这种背景下阅读这篇文章。让我们开始推动吧!
我认为你可以通过构建它的人如何描述它来了解很多东西。(如果你在推特上关注我,你会知道我一直在反复修改我们的描述!)
Next.js 将自己描述为
用于生产的 React 框架。Next.js 为你提供最佳的开发者体验,并具有生产所需的所有功能:混合静态和服务器渲染、TypeScript 支持、智能捆绑、路由预取等。无需配置。
Next.js 由 Vercel 构建。查看 Vercel 平台的 GitHub 代码仓库,它指出
Vercel 是一个用于静态网站和前端框架的平台,旨在与你的无头内容、商务或数据库集成。
我们将 Remix 描述为
Remix 是一个边缘原生的、全栈 JavaScript 框架,用于构建现代、快速且有弹性的用户体验。它使用 Web 标准统一了客户端和服务器,因此你可以减少对代码的思考,而更多地关注你的产品。
我们将把这些描述的对比留给你。
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 目录。当网站部署后,静态文件现在从边缘 (Vercel 的 CDN) 而不是从单个位置的源服务器提供服务。当请求到达时,CDN 只需提供该文件。数据加载和渲染已提前完成,因此访问者无需支付下载 + 渲染的成本。此外,CDN 是在全球范围内分布的,靠近用户(这就是“边缘”),因此对静态生成文档的请求不必一直到达单个源服务器。
为什么 Remix 移植版本这么快:Remix 不支持 SSG,因此我们使用了 HTTP stale-while-revalidate 缓存指令 (SWR,不要与 Vercel 的 swr
客户端获取软件包混淆)。最终结果是相同的:边缘的静态文档(即使在同一个 CDN Vercel 上)。不同之处在于文档如何到达那里。
不是在构建/部署时获取所有数据并将页面渲染为静态文档,而是在获得流量时预热缓存。文档从缓存中提供服务,并在后台为下一位访问者重新验证。与 SSG 一样,当你有流量时,没有访问者会支付下载 + 渲染的成本。如果你想知道缓存未命中,我们稍后会讨论一下。
SWR 是 SSG 的一个很好的替代方案。部署到 Vercel 的另一个好处是他们的 CDN 支持它。
你可能想知道为什么 Remix 移植版本不如 Next.js 快。由于 Remix 没有内置的图像优化(还没有),我们只是将图像指向 Next.js 应用程序🤫。浏览器必须打开与两个域的连接,这会使图像的加载延迟 0.3 秒(你可以在网络瀑布上验证这一点)。如果图像是自托管的,它将与另外两个图像的加载时间都在 0.7 秒左右。
为什么 Remix 重写版本这么快:这个版本不是使用 SSG 或 SWR 在边缘缓存文档,而是在边缘缓存数据,使用 Redis。事实上,它实际上还使用 Fly.io 在边缘运行应用程序。最后,它有一个快速的图像优化 资源路由,该路由写入到 持久卷。它基本上是它自己的 CDN 😎。
这在几年前可能很难构建,但在过去几年中,服务器环境发生了重大变化,并且只会变得更好。
Remix 和 Next.js 有什么不同?
这是我们接下来要回答的问题。在功能集方面有很多差异,但一个主要的架构差异是 Remix 不依赖 SSG 来提高速度。
在几乎每个应用程序中,你最终都会遇到 SSG 无法支持的情况。对于我们在此处比较的应用程序,就是搜索页面。
限制在于用户可以提交无限数量的查询。考虑到宇宙当前在空间和时间上的限制,你无法静态生成无限多的页面。因此,SSG(静态站点生成)是不可能的。
由于 SSG 无法扩展到动态页面,Next.js 切换到从用户浏览器进行客户端数据获取。查看网络瀑布图,我们将知道它为什么比 Remix 慢 2.3 倍。
在 Next.js 应用开始加载图片之前,Remix 应用已经完全加载完毕。在 Web 性能方面,最重要的事情可能是并行化网络瀑布图。在 Remix,我们对此非常狂热。
为什么 Next.js 更慢:Next.js 引入了我们称之为“网络瀑布请求链”的东西。由于这里不能使用 SSG,该应用程序从用户的浏览器获取搜索结果。它在获取数据之前无法加载图像,并且在加载、解析和执行 JavaScript 之前无法获取数据。
在客户端获取也意味着网络传输更多的 JavaScript,以及更多的解析/执行时间。有时我们忘记了解析/执行,但你可以看到第 15 个请求上的 JS 执行时间比文档下载的时间还要长!Next.js 发送的 JavaScript 比 Remix 多 1.5 倍,未压缩时为 566 kB 对比 371 kB。在网络上传输时,压缩后多了 50 kB(172 kB 对比 120 kB)。
在浏览器中做更多的工作开始累积。请查看底部行,它们显示了 CPU 利用率和浏览器主线程活动。Next.js 应用非常忙碌,有一个大的红色“长时间任务”拖慢了速度。
为什么 Remix 仍然和主页一样快:实际上,这两个 Remix 示例在请求中都不需要与 Shopify API 通信。虽然 SSG 不能缓存搜索页面,但 Remix 版本可以:使用 SWR 或 Redis。当你有一种单一的、动态的方式来生成页面时,你可以调整你的缓存策略,而无需更改应用程序代码。结果是在常用访问页面上达到 SSG 的速度。"/search"
页面以及左侧导航上的类别和像“tshirt”这样的常用查询很可能已经被预热。
是的,但是如果缓存未命中呢?
你可能不相信我这一点,我也没有办法证明我们的缓存是空的,但这是一个 Remix 中的缓存未命中 (我发誓,如果我说谎就让我遭天谴)。
实际上,我说谎了。那是 Remix 重写的缓存命中。 缓存未命中更快 (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 的这些问题,在 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 提供商都没有使用这两个(但确实必须导出它们的模块)。使用这两个网站,它们似乎具有相同的功能集。无论如何,如果我们确实遗漏了某些东西,当应用程序中可见的功能仅花费了十分之一的代码时,很难想象它会花费 7,000 行代码才能实现。
即使 Next.js 将搜索页面改用 getServerSideProps
,他们仍然需要几乎所有用于数据变更功能的代码,但我现在有点超前了!
我们经常谈论“部署到边缘”。这究竟是什么意思?这是另一次来自香港的缓存未命中,这次用户的网络速度很快。
这次我们将讨论两个 Remix 应用之间的差异。我们已经知道 Next.js 版本由于网络瀑布链而速度较慢。
两个 Remix 应用都在服务器上获取数据,那么为什么 Remix 端口比 Remix 重写落后这么多?
答案很简单:Remix 端口是在 Vercel 函数中运行的,而 Vercel 的函数不会在边缘运行你的代码,它们在一个区域中运行,默认为华盛顿特区。这离香港相当远!
这意味着用户必须从香港一路到达华盛顿特区,服务器才能开始从 Shopify 获取数据。当服务器完成时,它必须将文档一路发回。
Remix 重写也在华盛顿特区运行,但它也在香港运行!这意味着用户可以非常快速地到达 Remix 服务器,在那里一切都会更快。
这就像骑自行车到火车站进城,而不是骑自行车全程。
🚲-----------------------------------------🏢
🚲-----🚊====🏢
你可以在网络瀑布中看到这种情况(像往常一样)
基础设施的差异体现在文档的第一个蓝色条中。在 Remix 端口中,它要大得多。那是用户在 Vercel 函数自行车道上骑自行车环游世界的一半。在 Remix 重写中,它上了火车,更快地到达了 Shopify API 并返回。
这个版本在 Fly.io 上运行,它可以让 Node.js 服务器在全球数十个区域运行。但 Remix 并不依赖 Node.js。它可以运行在任何 JavaScript 环境中。事实上,它已经在 Cloudflare Workers 中运行,这意味着你正在 他们遍布全球的 250 台服务器上运行你的代码。没有比这更接近用户的了!
这就是我们说 Remix 是“边缘原生”的原因。Next.js 依赖于 Node.js,因此它今天部署到边缘的能力是有限的。
我们在这个领域还有很多工作要做,以改善开发人员的体验。我们目前只正式支持 Node.js 和 Cloudflare,但我们正在积极开发 Deno,并且社区成员已经在 Fastly 上运行了 Remix。
当你使用像 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 不需要为这种一流的异步处理交付任何代码字符。#useThePlatform ... 或者,呃,#reuseThePlatform 😎?!
这是 Remix 和 Next.js 开始看起来完全不同的地方。你一半的应用代码与数据变更有关。现在是你的 Web 框架尊重这一点的时候了。
Next.js 中变更的工作方式:Next.js 在这里不会为你做任何事情。<button onClick={itsAllUpToYou}>
。通常,你需要管理表单的状态以了解要发布的内容,添加一个 API 路由以发布到,自己跟踪加载和错误状态,重新验证数据并在整个 UI 中传播更改,最后处理错误、中断和竞争条件 (但说实话,没有人真正处理这些东西)。
Remix 中变更的工作方式:Remix 使用 HTML 表单。我知道你在想什么。
呸……我正在构建一个 Web 应用程序,这永远行不通。
你可能会认为你即将看到的 API 无法满足现代 Web 应用程序的需求。高度交互的 Web 应用程序一直是我整个职业生涯所做的事情,Remix 的设计考虑到了它们。仅仅因为它看起来像旧时代的 PHP,并不意味着它不能扩展到现代、复杂的用户体验。我们喜欢说 Remix 可以向上扩展,但也可以向下扩展。因此,让我们回到旧时代,帮助你理解 Remix。
自从 Web 诞生以来,变更被建模为一个表单和一个服务器页面来处理它。完全忽略 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);
}
浏览器导航到 "/add-to-cart"
,并使用表单的序列化数据进行 POST,添加待处理的 UI,并在完成后渲染一个新页面,其中包含数据库中的所有新数据。
Remix 做的事情与 HTML 表单相同,只是使用大写的 <Form>
和你的路由上名为 action
的函数进行了优化(想象一下你的 Next.js 页面是它们自己的 API 路由)。它使用 fetch
而不是文档重新加载进行 POST,然后使用服务器重新验证页面上的所有数据,以使 UI 与后端保持同步。这与你在 SPA 中习惯做的事情相同,只是 Remix 为你管理这一切。
除了表单和服务器端操作外,不需要任何应用程序代码来与服务器通信变更。也没有应用程序上下文提供程序或全局状态管理技巧来将更改传播到 UI 的其余部分。这就是为什么 Remix 包比 Next.js 包小近 30% 的原因,你不需要所有这些代码来与你的“API 路由”通信。
糟糕,我又撒谎了。该代码实际上可以在 Remix 中工作。如果你使用小写的 <form>
,浏览器会处理 POST,而不是 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 核心团队构建的 React 服务器组件示例时,他们也存在同样的错误。
我之前说过我们对网络选项卡非常着迷。让我们看看 Remix 如何处理这个问题。
你可以看到 Remix 在中断时取消请求,并在 POST 完成后重新验证数据。这确保了整个页面(不仅仅是这个表单)上的 UI 与你的表单刚刚对服务器所做的任何更改同步。
你可能会认为也许我们只是在我们的应用程序中比 Next.js 应用程序更注重细节。所有这些行为都不在应用程序代码中。它都内置在 Remix 的数据变更 API 中。(它实际上只是在做浏览器使用 HTML 表单所做的事情...)
Remix 中客户端和服务器之间的无缝集成和转换是前所未有的。
在我们数十年的 Web 开发生涯中,我们还记得以前是多么的简单。在表单中放置一个按钮,将其指向一个写入数据库的页面,重定向,获取更新的 UI。一切都如此简单。
在设计 Remix API 时,我们始终首先关注平台本身。比如 mutation 工作流。我们知道 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,无意中更精通 Web。
这是我们的核心价值观。虽然 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 更快的页面加载、更快的过渡、更好的 mutation 周围的 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 中,您的数据抽象还可以封装数据 mutation。所有代码都保留在服务器上,从而带来更好的应用程序代码和浏览器中更小的 bundle。
使用 Next.js,您必须将自己的数据 mutation 代码发送到浏览器,以与 API 路由进行交互,并将更新传播到 UI 的其余部分。正如我们在本文中看到的那样,即使是顶级团队也会在错误、中断和竞争条件方面搞砸。
您不是在忽略
getServerSideProps
吗?
有些人说您可以使用 getServerSideProps
完成 Remix 所做的一切。这个问题源于我们没有机会很好地解释 Remix!
如前所述,这肯定会加快搜索页面的速度。但是,您仍然需要处理数据 mutation。您需要结合使用 getServerSideProps
、API 路由以及您自己的浏览器代码(用于错误处理、中断、竞争条件、重定向和重新验证),这些代码与它们进行通信以进行 mutation。我们在这里真正说的是“您可以构建自己的 Remix”。实际上,您可以。我们已经做了 😇。
呼!
既然我们已经回答了大家一直在问我们的一个大问题,我们未来的帖子将真正开始展示 Remix 可以做什么!