Jamstack的一个新模式。分段式渲染

172 阅读12分钟

如果你认为静态渲染只限于通用的、公共的内容,对你网站的每个用户都是一样的,那么你一定要读读这篇文章。

分段渲染是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保持干净。完美。

摘要

要实现分段渲染。

  1. 为一个页面定义你的 "分段"。
    例如:付费用户与免费用户,来自A公司的用户与来自B或C公司的用户,等等。
  2. 渲染你所需要的页面的静态变化,每个部分有一个URL。
    例如。/with-jokes/my-article,/bland/my-article 。每个变体与一个细分市场相匹配,例如,付费或免费用户。
  3. 设置一个非常小的重定向服务器,检查HTTP请求的内容,并根据用户的细分市场,将其重定向到正确的变体。
    例如:付费用户被重定向到/with-jokes/my-article 。我们可以通过检查用户的请求cookies来判断用户是否付费。

下一步是什么?更多的性能!

现在,你可以在同一个页面上有任意多的变化。你优雅地解决了付费用户的问题。更好的是,你实现了一种新的模式--分段渲染,在不牺牲性能的情况下为Jamstack带来了个性化的服务。

最后一个问题:如果你有很多可能的组合会怎么样?比如5个参数,每个参数有10个值?你不能在构建时渲染无限多的页面--那会花费太长时间。而且,也许你在法国并没有任何付费用户,他们选择了light主题,属于A/B测试的B桶。有些变化甚至不值得渲染。

希望现代的前端框架能帮你解决这个问题。你可以使用一个中间模式,如Next的增量静态再生Gatsby的延迟静态生成,只在需要时渲染变化。

网站个性化是一个热门话题,但对性能和能源消耗来说是不利的。分段渲染优雅地解决了这一冲突,让你静态地渲染任何内容,无论是公共内容还是针对每个用户群的个性化内容。

关于此主题的更多资源

在Smashing杂志上的进一步阅读