如何用 Bunny.net Middleware Edge 脚本托管 Quartz 静态站点
Host Quartz Static Site on Bunny.net with Middleware Script
针对 Quartz SSG 按页面类型输出不同 HTML 结构(扁平 about.html 与 folder/index.html)导致 Bunny Edge Rules 无法区分的问题,作者用 Bunny CDN Middleware Edge 脚本实现了路由:先尝试加 .html,失败则回退到 /index.html,同时跳过首页和带扩展名的静态资源。
了解我如何使用 Bunny CDN Middleware Edge 脚本绕过 Quartz SSG 输出其公共 HTML 站点文件的方式,修复 Edge 规则的局限性。
前言
我写过一篇笔记,关于在尝试将我的 Quartz 站点托管到 Bunny.net Storage and CDN (联盟链接) 时遇到的 一个问题。我联系了 Bunny 支持团队,询问可能的建议,然后当晚就睡了。
回顾一下,我的问题是:
- Quartz emits two different HTML file structures depending on page type:
- 普通页面(例如
about.md)被输出为扁平的about.html文件。 - 文件夹/索引页面被输出为
about/index.md,或者自动生成的文件夹列表被输出为folder/index.html而不是folder.html。
- 普通页面(例如
- 使用 Bunny Edge Rules(基于模式/优先级)来进行扁平重写行不通,因为它不知道如何区分不同的页面类型。它无法进行基于文件系统的检查(例如 Nginx 中的
try_files),也无法在执行重写规则之前检查文件是否存在。
这篇文章适用于那些使用静态主机(可以是不是 Bunny Storage 的提供商)托管其 Quartz 构建的站点,并希望使用 Bunny CDN 来绕过 Quartz 输出其 HTML 文件结构的方式的人。
也就是说,托管一个 Quartz 站点,而不是:
- Cloudflare Pages
- GitHub Pages
- GitLab Pages
- Vercel
- Netlify
- Self-hosting / Web Server with
- Nginx
- Apache
- Caddy
可能的修复方法
Bunny 客户支持在两小时内回复了(他们的回复总是超快且有帮助!),并给了我两种可以尝试的方法:
1) Edge Rule
使用一个 Edge Rule,在初始 404 后进行 Origin Retry(如果添加 .html 不起作用,尝试添加 /index.html)。他们提供了一个工作流程,我会在这里分享给想要尝试的读者:
以下是你需要在 Edge Rule 配置页面中输入的内容:
操作:
覆盖 Origin URL
Origin URL:
https://your-origin.com{{path}}/index.html重试 Origin
条件:
如果匹配全部
- 状态码:匹配任意:404
- Origin 重试尝试次数:匹配任意:0(零)
2) Edge Script
不过,为了更好的可扩展性,请使用 Edge 脚本 来实现更程序化的路由逻辑,从而无需维护显式的目录列表。
我现有的 Edge 规则
在某些场景下,仅使用 Edge 规则就能奏效(我下面的方法):
- 你不使用 Quartz 索引/文件夹页面,或者
- 你有数量稳定的文件夹/目录页面,可以“设置后就不用管”
由于文件夹索引页面是可选的(参见 文件夹和标签列表 和 文件夹页面),使用几条 Edge 规则就足够了,只需将 .html 添加到除首页和资源之外的所有扁平页面。
供你参考,以下是我在 Bunny.net Storage + CDN 上为 Quartz v5.0.0 临时使用的 Edge 规则。提示:优先级很重要!如果你没有任何文件夹索引,跳过第一条规则。如果你的索引页面数量有限且稳定,请相应地将页面添加到第一条规则中(我这里有 /blog 和 /notes;请将其更改为你的索引页面 slug)。
但我想找到一个可扩展的解决方案——每次有新的索引/文件夹页面时手动添加到第一条规则,这不是我想做的事情。趁着我对 Quartz 的有限理解还记忆犹新,我决定尝试 Bunny Edge 脚本来解决这个 Quartz 托管问题。
Bunny 客户支持建议的第二个选项感觉更可靠,尽管相比免费的 Edge 规则需要额外费用。我觉得我永远不会超过第一个价格档位(100 万次请求),所以我很乐意支付使用 Edge 脚本的额外 0.22 美元基础费用。
Bunny.net 的最低月费为 1 美元/月。我最近写了一个/costs页面,如果你想了解更多。
修复需求
我需要一个中间件脚本来执行以下操作:
- 不要对主页或任何资源做任何处理(即排除任何带文件扩展名的内容)
- 假设第一个请求是纯 HTML 文件,添加
.html - 如果第 2 步没有成功,则回退到添加
/index.html - 确保静态资源(CSS/JS)保持不变,这样网站就不会崩溃
提示
在使用 Edge 脚本之前,请禁用 Edge 规则以防止操作重叠或错误。并且更改后一定要清除缓存!
寻找现有解决方案
但我对Deno没有经验,这是 Bunny Edge 脚本使用的基于 JS 的语言!学习 Deno 不在我的计划之内(掌握 TypeScript 已经让我够呛了!),所以我开始搜索是否有人写过关于在 Bunny.net(或其他静态托管)上托管 Quartz 构建的网站,而不是使用本文开头提到的托管提供商。
令我惊讶的是,居然真的有一篇文章,感觉就像是专门为我写的,作者是Grimtech — 在 Bunny CDN 上托管 Quartz 博客。我立即尝试按原样使用他的脚本,但不幸的是它没有用;也许是因为他用的是Quartz v4.5.2,而我用的是v5.0.0?
在 Bunny 脚本的浏览器内编辑器中,有一个实时预览窗格,让我可以测试请求 URL,看看规则是否被正确应用和路由。我尝试自己调整脚本以满足我的四个需求;但说实话,我完全不知道自己在做什么。🙃
我所做的更改存在语法问题,并且不断因重定向循环和 404 而失败。我不得不放弃,并求助于 LLM:Claude Haiku 4.5。我给了它我的需求,经过几次编辑和测试的尝试后,我得到了一个可行的解决方案!这里供参考:
Quartz 的中间件脚本修复
免责声明
一个 LLM(通过 Duck.ai 的 Claude Haiku 4.5)在我的指示下帮助生成了下面的脚本;我通过多次测试运行手动修订、改进并验证了它。使用前请先审查。
1import * as BunnySDK from "@bunny.net/edgescript-sdk"
2
3async function onOriginRequest(context: { request: Request }) {
4 const url = new URL(context.request.url)
5 const pathname = url.pathname
6
7 // Skip homepage and static assets with extensions
8 if (pathname === '/' || pathname.includes('.')) {
9 return context.request
10 }
11
12 const headers = new Headers(context.request.headers)
13 headers.set('X-Original-Path', pathname)
14
15 // Initial attempt:
16 // If it ends with a slash, assume folder index. If not, assume flat HTML file.
17 if (pathname.endsWith('/')) {
18 url.pathname = `${pathname}index.html`
19 } else {
20 url.pathname = `${pathname}.html`
21 }
22
23 return new Request(url.toString(), { ...context.request, headers })
24}
25
26async function onOriginResponse(context: { request: Request, response: Response }) {
27 if (context.response.status === 404) {
28 const originalPath = context.request.headers.get('X-Original-Path')
29 if (!originalPath) return context.response
30
31 const url = new URL(context.request.url)
32
33 // Fallback 1: User requested a folder without a slash (/blog), but it failed. Try index.html.
34 if (!originalPath.endsWith('/') && url.pathname.endsWith('.html')) {
35 url.pathname = `${originalPath}/index.html`
36 return fetch(url.toString(), context.request)
37 }
38
39 // Fallback 2: User requested a flat page with a slash (/about/), but index.html failed.
40 // It's a flat page, so 301 redirect to the clean URL (/about) to keep asset paths intact.
41 if (originalPath.endsWith('/') && url.pathname.endsWith('index.html')) {
42 const cleanPath = originalPath.slice(0, -1)
43 const flatTestUrl = new URL(url)
44 flatTestUrl.pathname = `${cleanPath}.html`
45
46 const flatCheck = await fetch(flatTestUrl.toString())
47 if (flatCheck.status === 200) {
48 return new Response(null, {
49 status: 301,
50 headers: { "Location": cleanPath }
51 })
52 }
53 }
54 }
55
56 return context.response
57}
58
59BunnySDK.net.http
60 .servePullZone()
61 .onOriginRequest(onOriginRequest)
62 .onOriginResponse(onOriginResponse)结论
我非常激动它能正常工作,因为我以为我不得不放弃使用 Bunny 了!使用Quartz对我来说是一段相当艰难的旅程;但我想这就是每次开始学习新技术时的感觉。WordPress 和 Hugo 一开始都让人困惑——我讨厌其中一个但喜欢另一个(顺便说一下,是 Hugo)。
我不认为我会像喜欢 Hugo 那样喜欢 Quartz 的后端,但没关系;作为静态网站生成器,我对 Quartz 的喜爱远胜于WordPress,无论哪一天都是如此!
我近期会再写一些关于 Quartz 自定义的内容,如果你感兴趣,可以考虑通过 RSS/Atom 订阅 subscribing。如果这篇文章对你有帮助,请考虑使用我的 Bunny.net 推荐链接,或者给我 打赏/买杯咖啡。感谢你的支持,希望这对你有帮助!
—Naty
来源:Hacker News · burgeonlab.com

