静态第一:带有无服务器渲染作为后备的预先生成的jamstack网站
JAMstack 架构正日益受到关注,它提供了一种高效的网站构建方法。
JAMstack 的核心原则之一是预渲染。这意味着提前将网站生成一系列静态资源,以便能够以最快的速度、最低的开销从 CDN 或其他优化的静态托管环境中为访问者提供服务。
但是,如果我们要提前预生成网站,如何让它们看起来更动态呢?如何构建需要频繁更改的网站?如何处理用户生成的内容?
实际上,这正是无服务器函数的理想应用场景。JAMstack 和无服务器是最佳拍档,它们完美地互补。
本文将探讨一种模式:在几乎完全由用户生成内容组成的网站中,使用无服务器函数作为预生成页面的后备方案。我们将采用一种乐观 URL 路由技术,其中 404 页面是一个无服务器函数,以便动态添加无服务器渲染。
听起来很复杂?也许吧。但它有效吗?绝对有效!
您可以试用演示网站来了解这个用例。但是,请您看完本文后再试用。
您回来了?太好了,让我们深入探讨。
这个示例网站的理念是:让您创建一条温馨的留言和虚拟鼓励信息发送给朋友。您可以撰写留言,自定义棒棒糖(或冰棒,为我的美国朋友们准备的)并获得一个 URL 与您的收件人分享。就这样,您就照亮了他们的一天。还有什么比这更好的呢?
传统上,我们会使用一些服务器端脚本处理表单提交,将新的棒棒糖(我们的用户生成内容)添加到数据库中并生成唯一的 URL。然后,我们会使用更多服务器端逻辑来解析对这些页面的请求,查询数据库以获取填充页面视图所需的数据,使用合适的模板进行渲染,并将其返回给用户。
这看起来合情合理。
但扩展的成本是多少?
技术架构师和技术主管在评估项目范围时经常会遇到这个问题。他们需要规划、支付和配置足够的资源以应对成功的情况。
这个虚拟棒棒糖网站并非普通的装饰品。由于我们都想互相发送积极的信息,这个网站将让我成为亿万富翁!随着消息的传播,流量水平将会飙升。我最好有一个好的策略来确保服务器能够处理繁重的负载。我可能会添加一些缓存层、一些负载均衡器,并且我会设计我的数据库和数据库服务器,以便能够分担负载,而不会因为创建和提供所有这些棒棒糖的需求而不堪重负。
但是……我不知道如何做这些事情。
而且我不知道添加这些基础设施并保持其正常运行的成本是多少。这很复杂。
这就是我喜欢通过预渲染尽可能简化我的托管的原因。
提供静态页面比从需要执行一些逻辑以按需为每个访问者生成视图的 Web 服务器动态提供页面要简单得多且成本更低。
由于我们正在处理大量用户生成的内容,使用数据库仍然是有意义的,但我不会自己管理它。相反,我将选择许多可用作服务的数据库选项之一。我将通过其 API 与它进行交互。
我可能会选择 Firebase、MongoDB 或其他任何数量的数据库。Chris 在一个关于无服务器资源的优秀网站上编译了一些这样的资源,非常值得探索。
在本例中,我选择 Fauna 作为我的数据存储。Fauna 提供了一个很好的 API 用于存储和查询数据。它是一个非 SQL 风格的数据存储,它正是我所需要的。
至关重要的是,Fauna 已将提供数据库服务作为一项完整的业务。他们拥有我永远不会拥有的深厚领域知识。通过使用像这样的数据库即服务提供商,我为我的项目继承了一个专家数据服务团队,包括高可用性基础设施、容量和合规性安心、熟练的支持工程师和丰富的文档。
与其自己动手,不如使用这样的第三方服务,这就是它的优势所在。
架构 TL;DR
在处理概念验证时,我经常发现自己会涂鸦一些逻辑流程。这是我为这个网站做的涂鸦:
以及一些解释:
- 用户通过填写普通的 HTML 表单创建一个新的棒棒糖。
- 新内容保存在数据库中,并且它的提交会触发新的站点生成和部署。
- 站点部署完成后,新的棒棒糖将可通过唯一的 URL 访问。它将是一个静态页面,从 CDN 快速提供服务,不依赖于数据库查询或服务器。
- 在站点生成完成之前,任何新的棒棒糖都将无法作为静态页面访问。对棒棒糖页面的不成功请求将回退到一个页面,该页面通过动态查询数据库 API 来动态生成棒棒糖页面。
这种方法首先假设静态/预生成的资源,然后在静态视图不可用时才回退到动态渲染,正如联合利华的 Markus Schork 所描述的那样,这被称为“静态优先”,我很喜欢这个说法。
更详细的说明
您可以直接深入研究该网站的代码(它是开源的,您可以随意探索),或者我们可以进一步讨论。
您想更深入地挖掘并探索此示例的实现?好的,我将更详细地解释:
- 从数据库获取数据以生成每个页面
- 使用无服务器函数将数据发布到数据库 API
- 触发完整的站点重新生成
- 在尚未生成页面时按需渲染
从数据库生成页面
稍后,我们将讨论如何将数据发布到数据库,但首先,让我们假设数据库中已经有一些条目。我们将要生成一个包含每个条目的页面的网站。
静态网站生成器非常擅长这一点。它们会处理数据,将其应用于模板,并输出准备提供服务的 HTML 文件。我们可以为此示例使用任何生成器。我选择 Eleventy 是因为它相对简单且站点生成速度快。
为了向 Eleventy 提供一些数据,我们有很多选择。一种方法是提供一些返回结构化数据的 JavaScript。这非常适合查询数据库 API。
我们的 Eleventy 数据文件将如下所示:
<code>// 设置与 Fauna 数据库的连接。 // 使用环境变量进行身份验证 // 并访问数据库。 const faunadb = require('faunadb'); const q = faunadb.query; const client = new faunadb.Client({ secret: process.env.FAUNADB_SERVER_SECRET }); module.exports = () => { return new Promise((resolve, reject) => { // 获取最新的 100,000 个条目(为了我们的示例) client.query( q.Paginate(q.Match(q.Ref("indexes/all_lollies")),{size:100000}) ).then((response) => { // 获取每个条目的所有数据 const lollies = response.data; const getAllDataQuery = lollies.map((ref) => { return q.Get(ref); }); return client.query(getAllDataQuery).then((ret) => { // 将数据发送回 Eleventy 以用于站点构建 resolve(ret); }); }).catch((error) => { console.log("error", error); reject(error); }); }) }</code>
我将此文件命名为 lollies.js,它将使其返回的所有数据在名为 lollies 的集合中可用于 Eleventy。
我们现在可以在我们的模板中使用该数据。如果您想查看获取该数据并为每个项目生成页面的代码,您可以在代码存储库中查看。
在没有服务器的情况下提交和存储数据
当我们创建一个新的棒棒糖页面时,我们需要将用户内容捕获到数据库中,以便将来可以将其用于填充给定 URL 的页面。为此,我们使用传统的 HTML 表单将数据发布到合适的表单处理程序。
表单看起来像这样(或在存储库中查看完整代码):
<code></code>
在我们的托管方案中没有 Web 服务器,因此我们需要设计一个地方来处理从此表单提交的 HTTP 发布请求。这是无服务器函数的完美用例。我正在为此使用 Netlify Functions。如果您愿意,可以使用 AWS Lambda、Google Cloud 或 Azure Functions,但我喜欢 Netlify Functions 工作流程的简单性,以及它将我的无服务器 API 和我的 UI 保留在同一个代码存储库中的事实。
避免将后端实现细节泄露到前端是一个好习惯。清晰的分离有助于使事情更易于移植和整洁。查看上面表单元素的 action 属性。它将数据发布到我网站上名为 /new 的路径,这并没有真正暗示它将与哪个服务进行通信。
我们可以使用重定向将它路由到我们喜欢的任何服务。我将它发送到我将在此项目中配置的无服务器函数,但如果我们愿意,可以轻松地将其自定义为将数据发送到其他地方。Netlify 为我们提供了一个简单且高度优化的重定向引擎,该引擎在 CDN 级别引导我们的流量,因此用户可以非常快速地路由到正确的位置。
下面的重定向规则(位于我的项目的 netlify.toml 文件中)将 /new 的请求代理到由 Netlify Functions 托管的名为 newLolly.js 的无服务器函数。
<code># 将“new”URL 解析为函数 [[redirects]] from = "/new" to = "/.netlify/functions/newLolly" status = 200</code>
让我们看看那个无服务器函数:
- 将新数据存储到数据库中,
- 为新页面创建一个新的 URL,并
- 将用户重定向到新创建的页面,以便他们可以看到结果。
首先,我们将需要各种实用程序来解析表单数据、连接到 Fauna 数据库并为新的棒棒糖创建易于阅读的短唯一 ID。
<code>const faunadb = require('faunadb'); // 用于访问 FaunaDB const shortid = require('shortid'); // 生成短唯一 URL const querystring = require('querystring'); // 帮助我们解析表单数据 // 首先,我们使用我们的数据库设置一个新的连接。 // 环境变量帮助我们安全地连接 // 到正确的数据库。 const q = faunadb.query const client = new faunadb.Client({ secret: process.env.FAUNADB_SERVER_SECRET })</code>
现在,我们将向处理对无服务器函数的请求添加一些代码。处理程序函数将解析请求以从表单提交中获取所需的数据,然后为新的棒棒糖生成一个唯一的 ID,然后将其作为新记录创建到数据库中。
<code>// 处理对无服务器函数的请求 exports.handler = (event, context, callback) => { // 获取表单数据 const data = querystring.parse(event.body); // 添加一个唯一的路径 ID。并记下它 - 我们稍后会将用户发送到它 const uniquePath = shortid.generate(); data.lollyPath = uniquePath; // 组装准备发送到数据库的数据 const lolly = { data: data }; // 在 fauna db 中创建棒棒糖条目 client.query(q.Create(q.Ref('classes/lollies'), lolly)) .then((response) => { // 成功!将用户重定向到此新棒棒糖页面的唯一 URL return callback(null, { statusCode: 302, headers: { Location: `/lolly/${uniquePath}`, } }); }).catch((error) => { console.log('error', error); // 错误!返回带有 statusCode 400 的错误 return callback(null, { statusCode: 400, body: JSON.stringify(error) }); }); }</code>
让我们检查一下我们的进度。我们有办法在数据库中创建新的棒棒糖页面。我们还有一个自动构建,它会为我们的每个棒棒糖生成一个页面。
为了确保为每个棒棒糖都有一套完整的预生成页面,我们应该在每次成功将新条目添加到数据库时触发重建。这非常容易做到。由于我们的静态网站生成器,我们的构建已经自动化了。我们只需要一种触发它的方法。使用 Netlify,我们可以定义任意数量的构建钩子。它们是 Webhook,如果它们收到 HTTP POST 请求,它们将重建和部署我们的站点。这是我在 Netlify 的站点管理控制台中创建的一个:
为了重新生成站点,包括数据库中记录的每个棒棒糖的页面,我们可以在将新数据保存到数据库后立即向此构建钩子发出 HTTP POST 请求。
这是执行此操作的代码:
<code>const axios = require('axios'); // 简化发出 HTTP POST 请求 // 触发新的构建以永久冻结此棒棒糖 axios.post('https://api.netlify.com/build_hooks/5d46fa20da4a1b70XXXXXXXXX') .then(function (response) { // 在无服务器函数的日志中报告 console.log(response); }) .catch(function (error) { // 描述无服务器函数日志中的任何错误 console.log(error); });</code>
您可以在完整的代码中看到它,它已添加到数据库插入的成功处理程序中。
如果我们在与收件人分享新棒棒糖的 URL 之前,愿意等待构建和部署完成,那么这一切都很好。但是我们并不耐心,当我们获得刚刚创建的棒棒糖的新 URL 时,我们会立即想要分享它。
遗憾的是,如果我们在站点完成重新生成以包含新页面之前访问该 URL,我们将得到 404。但令人高兴的是,我们可以利用这个 404。
乐观 URL 路由和无服务器后备方案
使用自定义 404 路由,我们可以选择将每个对棒棒糖页面的失败请求发送到一个页面,该页面可以直接在数据库中查找棒棒糖数据。如果我们愿意,我们可以在客户端 JavaScript 中执行此操作,但更好的方法是从无服务器函数动态生成一个准备查看的页面。
方法如下:
首先,我们需要告诉所有希望访问棒棒糖页面的请求(这些请求返回为空)改为转到我们的无服务器函数。我们通过在 Netlify 重定向配置中添加另一个规则来实现:
<code># 未找到的棒棒糖应该直接代理到 API [[redirects]] from = "/lolly/*" to = "/.netlify/functions/showLolly?id=:splat" status = 302</code>
只有在对棒棒糖页面的请求没有找到准备提供服务的静态页面时,才会应用此规则。它会创建一个临时重定向 (HTTP 302) 到我们的无服务器函数,该函数看起来像这样:
<code>const faunadb = require('faunadb'); // 用于访问 FaunaDB const pageTemplate = require('./lollyTemplate.js'); // JS 模板文字 // 设置和授权 Fauna DB 客户端 const q = faunadb.query; const client = new faunadb.Client({ secret: process.env.FAUNADB_SERVER_SECRET }); exports.handler = (event, context, callback) => { // 从请求中获取棒棒糖 ID const path = event.queryStringParameters.id.replace("/", ""); // 在 DB 中查找棒棒糖数据 client.query( q.Get(q.Match(q.Index("lolly_by_path"), path)) ).then((response) => { // 如果找到,则返回视图 return callback(null, { statusCode: 200, body: pageTemplate(response.data) }); }).catch((error) => { // 未找到或发生错误,将悲伤的用户发送到通用错误页面 console.log('Error:', error); return callback(null, { body: JSON.stringify(error), statusCode: 301, headers: { Location: `/melted/index.html`, } }); }); }</code>
如果对任何其他页面(不在站点的 /lolly/ 路径内)的请求返回 404,我们不会将该请求发送到我们的无服务器函数以检查棒棒糖。我们可以直接将用户发送到 404 页面。我们的 netlify.toml 配置允许我们通过在文件中添加更多回退规则来定义任意数量的 404 路由级别。文件中第一个成功的匹配将被采用。
<code># 未找到的棒棒糖应该直接代理到 API [[redirects]] from = "/lolly/*" to = "/.netlify/functions/showLolly?id=:splat" status = 302 # 真正的 404 可以直接转到这里: [[redirects]] from = "/*" to = "/melted/index.html" status = 404</code>
我们完成了!我们现在拥有一个“静态优先”的站点,如果尚未将 URL 生成静态文件,它将尝试使用无服务器函数动态渲染内容。
非常快速!
支持更大规模
每次创建新条目时都触发构建以重新生成棒棒糖页面的技术可能并非永远都是最佳的。虽然构建的自动化意味着重新部署站点非常简单,但当我们开始变得非常受欢迎时,我们可能希望开始限制和优化事情。(这只是时间问题,对吧?)
没关系。当我们要创建非常多的页面并且数据库中更频繁地添加内容时,以下是一些需要考虑的事情:
- 我们可以不为每个新条目触发重建,而是可以将站点重建为计划作业。也许这可以每小时或每天发生一次。
- 如果每天构建一次,我们可能决定只为过去一天提交的新棒棒糖生成页面,并将每天生成的页面缓存以供将来使用。构建中的这种逻辑将帮助我们支持大量棒棒糖页面,而不会使构建时间过长。但我不会在这里讨论构建内缓存。如果您好奇,您可以在 Netlify 社区论坛中询问。
通过将静态预生成资源与提供动态渲染的无服务器后备方案相结合,我们可以满足令人惊讶的广泛用例——同时避免需要配置和维护大量动态基础设施。
您还可以使用这种“静态优先”方法满足哪些其他用例?
以上是静态第一:带有无服务器渲染作为后备的预先生成的jamstack网站的详细内容。更多信息请关注PHP中文网其他相关文章!

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

您是否曾经在项目上需要一个倒计时计时器?对于这样的东西,可以自然访问插件,但实际上更多

关于Flex布局中紫色斜线区域的疑问在使用Flex布局时,你可能会遇到一些令人困惑的现象,比如在开发者工具(d...

在元素个数不固定的情况下如何通过CSS选择第一个指定类名的子元素在处理HTML结构时,常常会遇到元素个数不�...
