I was inspired by Turntrout4 to optimize my website more, and two changes took page load times from “fast enough” to “effectively instant: Switching from CloudFront5 to Bunny CDN6 to optimize CDN cache misses, and efficiently navigating with Micromorph7.
Bunny CDN
I’ve been serving my personal website from CloudFront (Amazon’s CDN) for years, which was nice because it costs a few cents a month, but it annoyed me that cache misses get served slowly from S3. In some cases, this can take several hundred milliseconds. Completely unacceptable!
I finally decided to look up if anyone would let me serve all of my files from the CDN all of the time, and apparently Bunny CDN61 does. It’s “expensive” (over 10 cents per GB per month!), but since my entire website is ~30 MB, I just told them to store the entire thing on SSDs in every edge region.
Result: Every page loads in ~40 ms from anywhere remotely near an edge location2, regardless of how recently anyone else has requested the page.
My “unacceptable” above is mostly tongue-in-cheek, but there really is something nice about every link loading instantly rather than in half a second.
The code to do this is also much simpler since Bunny CDN has a CLI that handles sync properly, and cache “misses”3 are so fast that I’m just not hot-caching HTML pages.
Micromorph
The second trick is Micromorph7, a tiny library that intercepts same-origin link clicks, fetches the destination HTML, diffs the DOM, and then applies the change instead of navigating.
Practically, that means:
- The browser never re-parses CSS, re-downloads fonts, or re-runs the theme-toggle script.
- Parts of the page that don’t change stay stable across navigations (Turntrout uses this to keep a video running8).
- The transition feels smoother.
I also was already using instant.page9, which prefetches links on hover so the HTML is already in the browser’s HTTP cache by the time you click.
There was one snag: By default, Micromorph intercepts all same-site links, which caused it to try to morph HTML pages into RSS feeds or EPUBs. I fixed that with an include whitelist that only morphs paths ending in .html or with no extension:
import listen from 'https://cdn.jsdelivr.net/npm/micromorph@0.4.5/spa/+esm';
listen({
include: (anchor) => {
if (anchor.origin !== location.origin) return false;
const lastSegment = anchor.pathname.split('/').pop();
return lastSegment === '' || !lastSegment.includes('.') || lastSegment.endsWith('.html');
}
});
Did it actually help?
Here’s the Lighthouse10 performance scores before and after all recent changes:
| Page | Before | After |
|---|---|---|
/ | 66 | 96 |
/my-rss-reader-is-done.html | 64 | 94 |
/pages/about-me.html | 80 | 99 |
And you can probably feel it for yourself if you click any of the links.
I assume there are other options for this, but this is the one everyone talks about and it’s going to cost me like $0.10/mo, so I didn’t look very hard for alternatives. ↩
Sadly, there aren’t edge locations in the Middle East, China, Russia, or most of Africa, so people in those countries may experience 80 ms load times. ↩
There are two layers of lookups in a CDN: the CDN edge (hot cache) and the origin (usually slow). With Bunny CDN + Bunny storage, the origin is on an SSD in the same region, so a cache miss only takes a few milliseconds to load into the hot cache. ↩
https://turntrout.com/design#deduplicating-html-requests - “The Design of This Website”
https://aws.amazon.com/cloudfront/ - “Low-Latency Content Delivery Network (CDN) - Amazon CloudFront - Amazon Web Services”
https://bunny.net/ - “bunny.net - The Global Edge Platform that truly Hops”
https://github.com/natemoo-re/micromorph - “GitHub: natemoo-re/micromorph”
https://turntrout.com/design#website-video-looping - “The Design of This Website”
https://instant.page/ - “instant.page”
https://developer.chrome.com/docs/lighthouse/performance/performance-scoring - “Lighthouse performance scoring | Chrome for Developers”