如何用 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,同时跳过首页和带扩展名的静态资源。
Learn how I use Bunny CDN Middleware Edge script to workaround the way Quartz SSG outputs its public HTML site files, fixing the limit of Edge rules.
Preface
I wrote a note about an issue I was facing when trying to host my Quartz site on Bunny.net Storage and CDN (affiliate link). I reached out to Bunny support and asked for possible suggestions and went to bed that night.
To recap, my problem is:
- Quartz emits two different HTML file structures depending on page type:
- Regular pages (e.g.
about.md) are output as flatabout.htmlfiles. - Folder/index pages are output like
about/index.md, or auto-generated folder listings asfolder/index.htmland notfolder.html.
- Regular pages (e.g.
- Using Bunny Edge Rules (which are pattern/priority based) to do a flat rewrite will not work because it doesn’t know how to differentiate between the different page types. It cannot do file-system based checks (e.g.
try_filesin Nginx) nor can it check if a file exists before executing the rewrite rules.
This post is relevant for those who are using a static host (can be a provider that is not Bunny Storage) to host their Quartz-built site and want to use Bunny CDN to work around the way Quartz outputs its HTML file structure.
That is, to host a Quartz site that is not:
- Cloudflare Pages
- GitHub Pages
- GitLab Pages
- Vercel
- Netlify
- Self-hosting / Web Server with
- Nginx
- Apache
- Caddy
Possible Fixes
Bunny Customer Support replied within two hours (always had superfast and helpful replies from them!) and gave me two methods to try:
1) Edge Rule
Use an Edge Rule with Origin Retry after an initial 404 (if adding .html didn’t work, try adding /index.html). They provided a workflow which I will share here for readers who want to give it a try:
Here is what you’d enter in the Edge Rule configuration page:
Actions:
Override Origin URL
Origin URL:
https://your-origin.com{{path}}/index.htmlRetry Origin
Conditions:
IF Match all
- Status Code: Match any: 404
- Origin Retry Attempt Count: Match any: 0 (zero)
2) Edge Script
For better scalability though, use Edge scripts for more programmatic routing logic, removing the need to maintain an explicit list of directories.
My Existing Edge Rules
There are some scenarios where using only Edge rules will work (my method below):
- You do not use Quartz index/folder pages, or
- You have a stable amount of folder / directory pages which you can “set and forget”
Since folder index pages are optional (see Folder and Tag Listings and Folder Page), using a few Edge rules will be enough by adding.html to all flat pages except the homepage and assets.
For your reference, here were my temporary Edge rules that works for Quartz v5.0.0 on Bunny.net Storage + CDN. Tip: The priority matters! If you don’t have any folder indexes, skip the first rule. If you have a limited and stable number of index pages, add the pages accordingly to the first rule (where I have /blog and /notes; change it to your index page slugs).
But I want to find a solution that would scale—manually adding to the first rule every time there’s a new index/folder page is not something I want to do. With my limited understanding of Quartz was still fresh in my mind, I decided to try Bunny Edge scripts to fix this Quartz hosting problem.
The second option suggested by Bunny’s Customer Support felt more reliable despite requiring an additional cost compared to Edge rules which are free. I don’t think I’ll ever surpass the first price increment (1 million requests), so I’m happy paying the extra $0.22 base charge of using Edge scripts.
Bunny.net has a minimum monthly charge of $1 USD / month. I wrote a /costs page recently if you want to learn more.
Fix Requirements
I need a Middleware script to do the following:
- Do not do anything to the homepage or any assets (i.e. exclude anything with file extensions)
- Assume first request is a flat HTML file, add
.html - Fallback to adding
/index.htmlif step 2 didn’t work - Ensure static assets (CSS/JS) stay the same, so site doesn’t break
Tip
Before using Edge scripts, disable Edge rules to prevent overlapping actions or errors. And always purge cache after changes!
Searching for Existing Solutions
But I have no experience with Deno, the JS-based language Bunny Edge scripts use! Learning Deno is not on my bingo card (getting the hang on TypeScript is already pushing the boundary for me!) so I went searching if anyone has written about hosting Quartz-built sites on Bunny.net (or other static hosts) as opposed to using hosting providers mentioned at the beginning of this post.
To my surprise, there’s actually a post that felt as if it was written just for me by Grimtech — Quartz blog hosting on Bunny CDN. I immediately tried using his script as-i, butt unfortunately it didn’t work; maybe it is because he’s on Quartz v4.5.2 and I’m on v5.0.0?
In the Bunny Script in-browser editor, there’s a live preview pane which lets me test the request URLs to see if the rules are being applied and routed correctly. I gave it a shot at tweaking the script myself to accomplish my four requirements; but to be honest, I had no clue what I was doing. 🙃
The changes I made had syntax issues and kept failing with redirect loops and 404s. I had to give up and resorted to getting help from an LLM: Claude Haiku 4.5. I gave it my requirements and after a few trials of editing and testing, I got a working solution! Here it is for reference:
Middleware Script Fix for Quartz
Disclaimer
An LLM (Claude Haiku 4.5 via Duck.ai) helped generate the script below under my instruction; where I revised, improved, and validated it manually by multiple test runs. Please review before use.
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)Conclusion
I’m more than thrilled to have it working as I thought I had to give up on using Bunny! Working with Quartz has been a rather hard journey for me; but I guess that was how it felt every time I started learning a new technology. WordPress and Hugo both felt confusing at the start—I hated one but loved the other (which is Hugo by the way).
I don’t think I’ll ever love Quartz’s backend like I do Hugo, but that’s okay; I’m much happier with Quartz as a static site generator than WordPress any day of the week!
I’ll be writing some more about the customizations on Quartz in the near future, so consider subscribing via RSS/Atom feeds if you’re into that. If this article helped you, please consider using my Bunny.net referral link or send me a tip/coffee. Thank you for your support and hope this helps you!
—Naty
来源:Hacker News · burgeonlab.com

