跳到正文
Hacker News· meysamazad·· 7 天前AI 评分12

如何用 Bunny.net Middleware Edge 脚本托管 Quartz 静态站点

Host Quartz Static Site on Bunny.net with Middleware Script

AI 导读

针对 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 flat about.html files.
    • Folder/index pages are output like about/index.md, or auto-generated folder listings as folder/index.html and not folder.html.
  • 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_files in 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:

Flowchart showing how to use a Origin Retry Bunny Edge rule

(Note: I didn’t test this method)

Here is what you’d enter in the Edge Rule configuration page:

Actions:

  1. Override Origin URL

    Origin URL: https://your-origin.com{{path}}/index.html

  2. Retry 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).

Screenshot of Bunny.net Edge Rule config page with three rules to handle Quartz-built sites

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.

Bunny.net price breakdown

With the new additional $0.22, I’m still within the minimum! 👍

Fix Requirements

I need a Middleware script to do the following:

  1. Do not do anything to the homepage or any assets (i.e. exclude anything with file extensions)
  2. Assume first request is a flat HTML file, add .html
  3. Fallback to adding /index.html if step 2 didn’t work
  4. 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