如果你认为静态渲染只限于通用的、公共的内容,对你网站的每个用户都是一样的,那么你一定要读读这篇文章。
分段渲染是Jamstack的一种新模式,可以让你静态地个性化内容,而不需要任何形式的客户端渲染或每请求服务器端渲染。有许多用例:个性化、国际化、主题化、多租户、A/B测试......
让我们专注于一个对博客所有者非常有用的场景:处理付费内容。
祝贺你的新工作
哇,你刚刚被提升了!你现在是 "性能主管"。你现在是Repairing杂志的 "性能主管",这是Smashing杂志最严重的竞争对手。Repairing杂志有一个非常奇特的商业模式。每篇文章中的诙谐笑话,只有付费用户才能看到。
程序员为什么要过马路?
我打赌你会花钱知道答案的。
你今天的工作是以最好的表现来实现这个功能。让我们看看你如何能做到这一点。提示:我们将引入一种名为 "分段渲染 "的新模式。
用现代JavaScript框架渲染网页的多种方法
Next.js的流行源于它对 "渲染三部曲 "的掌握:在一个框架中结合客户端渲染、每请求服务器渲染和静态渲染的能力。
CSR、SSR、SSG......让我们弄清楚它们是什么
修复杂志的用户界面依赖于一个现代的JavaScript库,React。像其他类似的UI库一样,React提供了两种渲染内容的方式:客户端和服务器端。
客户端渲染(CSR)发生在用户的浏览器中。在过去,我们会使用jQuery来做CSR。
服务器端渲染发生在你自己的服务器上,要么在请求时(SSR),要么在构建时(静态或SSG)。SSR和SSG也存在于JavaScript的生态系统之外。例如,想想PHP或Jekyll。
让我们看看这些模式如何适用于我们的用例。
CSR。丑陋的加载器问题
客户端渲染(CSR)会在浏览器中使用JavaScript,在页面加载后添加诙谐的笑话。我们可以使用 "fetch "来获取笑话的内容,然后将其插入DOM中。
// server.js
const wittyJoke =
"Why did the programmer cross the road?\
There was something he wanted to C.";
app.get("/api/witty-joke", (req) => {
if (isPaidUser(req)) {
return { wittyJoke };
} else {
return { wittyJoke: null };
}
});
// client.js
const ClientArticle = () => {
const { wittyJoke, loadingJoke } = customFetch("/api/witty-jokes");
// THIS I DON’T LIKE...
if (loadingJoke) return <p>Ugly loader</p>;
return (
<p>
{wittyJoke
? wittyJoke
: "You have to pay to see jokes.\
Humor is a serious business."}
</p>
);
};
CSR涉及冗余的客户端计算和大量丑陋的加载器。
它是可行的,但它是最好的方法吗?你的服务器将不得不为每个读者提供诙谐的笑话。如果有什么东西使JavaScript代码失效,付费用户就不会有他们的乐趣,可能会生气。如果用户有一个缓慢的网络或一台缓慢的电脑,在他们的笑话被下载时,他们会看到一个丑陋的加载器。请记住,大多数访问者是通过移动设备浏览的!
随着API调用次数的增加,这个问题只会越来越严重。请记住,一个浏览器只能并行地运行少数几个请求(通常每个服务器/代理有6个)。服务器端渲染不受这个限制,在从你自己的内部服务中获取数据时,会更快。
每个请求的SSR。被第一个字节咬住了
按请求的服务器端渲染(SSR)在服务器上按需生成内容。如果用户是付费的,服务器会直接返回完整的文章作为HTML。否则,它会返回没有任何乐趣的平淡无奇的文章。
// page.js: server-code
async function getServerSideProps(req) {
if (isPaidUser(req)) {
const { wittyJoke } = getWittyJoke();
return { wittyJoke };
} else {
return { wittyJoke: null };
}
}
// page.js: client-code
const SSRArticle = ({ wittyJoke }) => {
// No more loader! But...
// we need to wait for "getServerSideProps" to run on every request
return (
<p>
{wittyJoke
? wittyJoke
: "You have to pay to see jokes. Humor is a serious business."}
</p>
);
};
SSR删除了客户端的计算,但没有删除加载时间。
我们不再依赖客户端的JavaScript了。然而,为每一个请求渲染文章是不节能的。第一个字节的时间(TTFB)也增加了,因为我们必须等待服务器完成其工作,然后才开始看到一些内容。
我们用一个更加丑陋的空白屏幕取代了丑陋的客户端加载器!现在我们甚至要为之付费!"。而现在我们甚至要为此付出代价!
缓存控制策略 "stale-while-revalidate "可以通过提供页面的缓存版本来减少TTFB问题,直到它被更新。但是对于个性化的内容来说,它并不是开箱即用的,因为它对每个URL只能缓存一个版本的页面,而不考虑cookies,也不能处理提供付费内容所需的安全检查。
静态渲染。解决富人/穷人问题的关键
在这一点上,你遇到了我所说的 "富人的客人/穷人的顾客 "问题:你的高级用户得到最差的性能,而不是得到最好的。
根据设计,与静态渲染相比,客户端渲染和每请求服务器端渲染涉及最多的计算,而静态渲染在构建时只发生一次。
我所知道的99%的网站都会选择CSR或SSR,并遭受富人的客人/穷人的问题。
深入了解分段式渲染
分段渲染只是一种更聪明的静态渲染方式。一旦你理解了这是关于缓存渲染,然后为每个请求获得正确的缓存渲染,一切都会变得妥当。
静态渲染能提供最好的性能,但灵活性较差
静态网站生成(SSG)在构建时生成内容。这是最有效的方法,因为我们只渲染一次文章。然后以纯HTML形式提供。
这就解释了为什么在构建时进行预渲染是Jamstack理念的基石之一。作为一个新晋升的 "性能主管",这绝对是你想要的!
截至2022年,所有Jamstack框架的静态渲染方法都大致相同。
- 你计算一个所有可能的URL列表。
- 为每个URL渲染一个页面。
const myWittyArticles = [
"/how-to-repair-a-smashed-magazine",
"/segmented-rendering-makes-french-web-dev-famous",
"/jamstack-is-so-2022-discover-haystack",
];
静态渲染的第一步的结果:计算一堆你将预渲染的URL。对于一个博客来说,它通常是你所有文章的列表。在第二步中,你只需渲染每篇文章,每个URL一个。
这意味着,一个URL严格来说等于一个版本的页面。你不能在同一个URL上有付费和免费版本的文章,即使是针对不同的用户。URL/how-to-repair-a-smashed-magazine 将向每个人提供相同的HTML内容,没有任何个性化的选项。不可能考虑到请求cookies。
分段渲染可以更进一步,为同一个URL渲染不同的变化。让我们来学习一下。
解除URL和页面变化的束缚
允许个性化内容的最天真的解决方案是在URL中添加一个新的路由参数,例如,"带笑话 "和 "平淡"。
const premiumUrl = "/with-jokes/how-to-repair-a-smashed-magazine";
const freeUrl = "/bland/how-to-repair-a-smashed-magazine";
用Next.js实现的方案大致是这样的。
async function getStaticPaths() {
return [
// for paid users
"/with-jokes/how-to-repair-a-smashed-magazine",
// for free users
"/bland/how-to-repair-a-smashed-magazine",
];
}
async function getStaticProps(routeParam) {
if (routeParam === "with-jokes") {
const { wittyJoke } = getWittyJoke();
return { wittyJoke };
} else if (routeParam === "bland") {
return { wittyJoke: null };
}
}
第一个函数为同一篇文章计算两个URL,一个是有趣的,一个是平淡的。第二个函数得到了这个笑话,但只针对付费版本。
很好,你有两个版本的文章。我们可以开始看到 "分段渲染 "中的 "分段"--付费用户与免费用户,每个分段都有一个渲染版本。
但现在,你有一个新问题:如何将用户重定向到正确的页面?很简单:把用户重定向到正确的页面,字面上的意思是!有了服务器和所有!
一开始可能听起来很奇怪,你需要一个网络服务器来实现高效的静态渲染。但请相信我:为静态网站实现最佳性能的唯一方法是做一些服务器优化。
关于 "静态 "主机的说明
如果你来自Jamstack的生态系统,你可能会爱上静态主机。有什么比推送几个文件,让你的网站在GitHub Pages上运行更美好的感觉?或者直接在内容交付网络(CDN)上托管一个成熟的应用程序?
然而,"静态托管 "并不意味着没有服务器。它意味着你不能控制服务器。仍然有一个服务器负责将每个URL指向正确的静态文件。
静态主机应该被看作是一个有限的,但便宜的和性能良好的选择,以托管一个个人网站或公司的登陆页面。如果你想超越这一点,你将需要对服务器进行控制,至少要处理诸如基于请求cookies或头文件的重定向等事情。
不过不需要找后端专家。我们不需要任何形式的花哨的计算。一个非常基本的重定向服务器,可以检查用户是否付费就可以了。
好消息:现代主机,如Vercel或Netlify实现了边缘处理程序,这正是我们在这里需要的。Next.js将这些Edge处理程序实现为 "中间件",因此你可以在JavaScript中对其进行编码。
边缘 "意味着计算尽可能地发生在终端用户身边,而不是有几个大的集中式服务器。你可以把它们看作是你核心基础设施的外墙。它们非常适合个性化,这通常与用户的实际地理位置有关。
用Next.js中间件轻松重定向
Next.js中间件的速度非常快,代码也非常简单。与AWS Gateway等云代理或Nginx等开源工具相反,中间件是用JavaScript编写的,使用Web标准,即fetch API。
在 "分段渲染 "架构中,中间件只是负责将每个用户请求指向正确的页面版本。
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
async function middleware(req: NextRequest) {
// isPaidFromReq can read the cookies, get the authentication token,
// and verify if the user is indeed a paid member or not
const isPaid = await isPaidFromReq(req);
const routeParam = isPaid ? "with-jokes" : "bland";
return NextResponse.redirect(
/${routeParam}/how-to-repair-a-smashed-magazine
);
}
一个中间件,为付费和免费用户实现分段渲染。
好了,就这样吧。你作为 "性能主管 "的第一天就结束了。你已经拥有了为你奇怪的商业模式实现最佳性能所需的一切
当然,你可以将这种模式应用于许多其他用例:国际化的内容、A/B测试、浅色/深色模式、个性化......你的页面的每个变化都构成了一个新的 "细分市场":法国用户、喜欢深色主题的人,或者付费用户。
顶部的樱桃:URL重写
但是,你是 "性能主管",而不是 "性能平均"!你希望你的网络应用是完美的。你希望你的网络应用是完美的,而不仅仅是好的你的网站当然在所有指标上都非常快,但现在你的文章URL看起来像这样。
/bucket-A/fr/light/with-jokes/3-tips-to-make-an-url-shorter
这不是真的好看......分段渲染很好,但终端用户不需要意识到自己的 "分段"。对好的工作的惩罚是更多的工作,所以让我们加上最后的修饰:不使用URL重定向,而使用URL重写。它们是完全一样的,只是你不会在URL中看到参数。
// A rewrite won’t change the URL seen
// by the end user => they won’t see the "routeParam"
return NextResponse.rewrite(/${routeParam}/how-to-repair-a-smashed-magazine);
URL/how-to-make-an-url-shorter ,没有任何路由参数,现在将根据用户的cookies显示正确的页面版本。路由参数仍然 "存在 "于你的应用程序中,但终端用户看不到它,而URL保持干净。完美。
摘要
要实现分段渲染。
- 为一个页面定义你的 "分段"。
例如:付费用户与免费用户,来自A公司的用户与来自B或C公司的用户,等等。 - 渲染你所需要的页面的静态变化,每个部分有一个URL。
例如。/with-jokes/my-article,/bland/my-article。每个变体与一个细分市场相匹配,例如,付费或免费用户。 - 设置一个非常小的重定向服务器,检查HTTP请求的内容,并根据用户的细分市场,将其重定向到正确的变体。
例如:付费用户被重定向到/with-jokes/my-article。我们可以通过检查用户的请求cookies来判断用户是否付费。
下一步是什么?更多的性能!
现在,你可以在同一个页面上有任意多的变化。你优雅地解决了付费用户的问题。更好的是,你实现了一种新的模式--分段渲染,在不牺牲性能的情况下为Jamstack带来了个性化的服务。
最后一个问题:如果你有很多可能的组合会怎么样?比如5个参数,每个参数有10个值?你不能在构建时渲染无限多的页面--那会花费太长时间。而且,也许你在法国并没有任何付费用户,他们选择了light主题,属于A/B测试的B桶。有些变化甚至不值得渲染。
希望现代的前端框架能帮你解决这个问题。你可以使用一个中间模式,如Next的增量静态再生或Gatsby的延迟静态生成,只在需要时渲染变化。
网站个性化是一个热门话题,但对性能和能源消耗来说是不利的。分段渲染优雅地解决了这一冲突,让你静态地渲染任何内容,无论是公共内容还是针对每个用户群的个性化内容。
关于此主题的更多资源
- "让我们把Jamstack带到SaaS。介绍彩虹渲染,"Eric Burel
我的第一篇文章,描述了分段渲染(又称 "彩虹渲染")的通用理论架构。 - "用Http Cache和分段渲染正确对待你的用户",Eric Burel
我的第二篇文章展示了使用Next.js中间件的实现。 - "Render Anything Statically With Next.js and the Megaparam," Eric Burel
我的第三篇文章,仅用HTTP缓存进行分段渲染(如果你使用Remix,你会喜欢它)。 - "Incremental Static Generation For Multiple Rendering Paths",Eric Burel
关于Next.js的GitHub ticket是这一切的开始。 - "Theoretical Foundations For Server-side Rendering and Static-rendering," Eric Burel
我的研究论文草稿,描述了SSR背后的数学原理。它证明了分段渲染在任何情况下都能达到最佳的渲染数量。你无法超越这一点! - "High Performance Personalization With Next.js Middleware," Raymond Cheng
一个优秀的使用案例和Plasmic的演示。 - "用Next.js中间件进行A/B测试," Raymond Cheng
同样来自Plasmic,分段渲染被应用于A/B测试。 - "Use Eleventy Edge To Deliver Dynamic Web Sites On The Edge," Eleventy Edge Blog
Eleventy Edge,11ty和Netlify Edge Handlers之间的深度整合,实现个性化。 - "Vercel/Platforms",Steven Tey
Vercel Platforms,多租户的分段渲染的例子。 - "Avoid Waterfalls Of Queries In Remix Loaders," Sergio Xalambrí
如何正确并行化你的数据获取请求(以Remix为例)。
在Smashing杂志上的进一步阅读
- "Jamstack Rendering Patterns:演变",Ekene Eze
- "Next.js的状态管理",Átila Fassina
- "Jamstack CMS。过去、现在和未来》,Mike Neumegen
- "使用Next.js的增量静态再生(ISR)完整指南》,Lee Robinson