Premature Optimisation
This post is a sort of small addendum to the last one. I realised that I could do a lot more to squeeze out performance from this blog, and decided to write about it.
Let's look at changes I did to the nginx config. And uhh, I don't know how I'm going to explain myself with this. I'll just let the change speak for itself. I apologise in advance.
location ~* \.(?:png|jpg|jpeg|avif|webp|gif|ico|svg|mp4|mp3|webm)$ {
expires 30d;
add_header Cache-Control "public";
}
# CSS and JS are cache busted with SvelteKit asset hashing
# and fonts don't change
location ~* \.(?:css|js|woff|woff2)$ {
expires max;
add_header Cache-Control "public";
}
Yeahhhhhh soooooo… I didn't have a cache this entire time. In fact for the entire 7 years of this blog's existence, I don't think I've ever setup a cache for it.
OK CHILL, I GET IT! I know this is an irredeemable oversight. It's practically free performance, and I just fucking ignored it for 7 years. I literally went to write my own DNS server for geolocation routing before adding 4 lines to my nginx config ffs.
Chat am I cooked? Like what the fuck is wrong with me?
But to be fair, a cache isn't the most important thing for a blog. Initial load times are more important than subsequent navigations as most users only read a single post then leave. But still bro, like wtf, you know???
Anyway, I also improved on the gzipping nginx does. Things were being gzipped before, but this configuration expands the list of files to be compressed. As well as some slight tuning to how/when files are gzipped.
gzip on;
gzip_disable "msie6";
gzip_proxied any;
gzip_vary on;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_min_length 50;
gzip_types
text/css
text/plain
text/javascript
application/javascript
application/json
application/x-javascript
application/xml
application/xml+rss
application/xhtml+xml
application/x-font-ttf
application/x-font-opentype
application/vnd.ms-fontobject
application/rss+xml
application/atom_xml
image/svg+xml
image/x-icon;
I've also added rate limiting to prevent abuse. It won't stop anything more than a small (D)DoS, but that's probably good enough.
limit_req_zone $binary_remote_addr zone=documents:10m rate=1r/s;
limit_req_zone $binary_remote_addr zone=assets:10m rate=5r/s;
location / {
[...]
limit_req zone=documents burst=2 nodelay;
}
location ~* \.(?:png|jpg|jpeg|avif|webp|gif|ico|svg|mp4|mp3|webm)$ {
[...]
limit_req zone=assets burst=50 nodelay;
}
location ~* \.(?:css|js|woff|woff2)$ {
[...]
limit_req zone=assets burst=15 nodelay;
}
location = /rss {
[...]
limit_req zone=documents burst=2 nodelay;
}
location = /atom {
[...]
limit_req zone=documents burst=2 nodelay;
}
There are a few other things I did, but they are boring to talk about so let's move on.
The images in this blog have always been one of it's biggest problems in regards to performance. I am just way too fucking lazy to actually optimise them before publishing them on the site. And the weird shit I do with dithering has not actually helped very much. (1) (1) But I do think it's cool, so it's going to stay for the indefinite future.
That all led me to going through the "Best Practices" section of the SvelteKit docs and finding this nice page about images. That's exactly what I need! So what are the best practices for images??!! Let's read it together.
Images can have a big impact on your app's performance. For best results, you should optimize them by doing the following:
YES!! TELL ME!!
- generate optimal formats like .avif and .webp
Ok, as I said before, I'm too lazy to do that. That isn't going to happen.
- create different sizes for different screens
Yeah nah mate. That's way too much work.
- ensure that assets can be cached effectively
I already do that. Well, as of like 3 days ago I do, but that counts!
Doing this manually is tedious. There are a variety of techniques you can use, depending on your needs and preferences.
Ohhh?? Let's read a little further on.
@sveltejs/enhanced-imgis a plugin offered on top of Vite's built-in asset handling. It provides plug and play image processing that serves smaller file formats like avif or webp, automatically sets the intrinsic width and height of the image to avoid layout shift, creates images of multiple sizes for various devices, and strips EXIF data for privacy. It will work in any Vite-based project including, but not limited to, SvelteKit projects.
Holy shit! "plug and play," "automatically," "will work in any […] SvelteKit project[s]"????
THESE ARE THE WORDS I WANT TO HEAR.
Use in your
.sveltecomponents by using<enhanced:img>rather than<img>and referencing the image file with a Vite asset import path:
<enhanced:img src="./path/to/your/image.jpg" alt="An alt text" />
OH MY BIDOOF! I just need to switch out img tags!!! Fuck yeah! That's just a one line change in my Markdown processor! I'm putting this in right now!
Okie dokie, I just changed the <img> regex to <enhanced:img>, and now let's go and feast on the fruits of our labour and hard work! I think going to "A Tour Of My Art Gallery" would probably be the best place to appreciate the results, since it's the post with the most amount of images.

Wait… huh? Wh-where are the… images???
WHY AREN'T THERE ANY IMAGES?
No!! This can't be happening!! No!!!!!! I was promised "plug and play!!" I PLUGGED SO WHY AREN'T I PLAYING?!!
Don't tell me I'm going to have to do work to fix this! No! I don't want to!
NOOOO! WHY CAN'T IT JUST WORK??
ASDJALKWDJAWLKDDHAFLUAWIDNA!11!!1
FUCK SHIT MERDE PUTAIN CHIER FUCK.
;(
Alright. So at build time, the blog takes posts written in Markdown and converts them into HTML be displayed on the webpage. The HTML is injected into the webpage through the {@html ...} expression Svelte provides. That's probably a good place to start, let's… read the docs… I guess.
[…] It also will not compile Svelte code.
Okay, yeah that explains it. Turning <img> into <enhanced:img> doesn't work because <enhanced:img> is a Svelte component which doesn't get rendered in the {@html ...} expression.
Fine… I see how it is.
I think I have an idea on how to solve this. But it's like… y'know… work. And I really don't feel lik-
Arghh, fine I'll do it. But just because you asked nicely.
So I wrote this Svelte component which splits an HTML string by regex rules, then dynamically renders matched parts as Svelte components with specific props, or as raw HTML if no match is found.
<script>
export let html;
export let rules = [];
const partitionHtml = (html, rules) => {
return rules.reduce(
(acc, rule) => {
return acc.flatMap((substr) => substr.split(rule.split));
},
[html]
);
};
const findAndTransform = (array, predicate, transform) => {
const foundElement = array.find(predicate);
return foundElement ? transform(foundElement) : null;
};
</script>
{#each partitionHtml(html, rules) as part}
{@const match = findAndTransform(
rules,
(rule) => rule.split.test(part),
(rule) => ({ ...rule, props: { ...rule.props, matches: [...part.match(rule.match)] } })
)}
{#if match}
<svelte:component this={match.component} {...match.props} />
{:else}
{@html part}
{/if}
{/each}
And here is how it's used. DitheredImage is the component that creates the <enhanced:img>.
<InjectableHTML
html={data.post.html}
rules={[
{
split: RegExp('(<img src="/img/.*".*/>)', 'i'),
match: RegExp('src="/img/(.*?)" alt="(.*?)"', 'i'),
component: DitheredImage
}
]}
/>
I also changed the entire structure of images on this site so that I could use Vite imports with them, and did some other stuff to keep the RSS feed working, but I'd rather not get into it.
So that's cool… I guess. It works now. yay. woo hoo. That only took like an hour or two, which is… well it is what it is.
But this entire thing has got me thinking.
Why am I doing any of this?
I've put so much work into this blog. With all it's iterations, I probably have over a 100 hours of work put into this (on just code, not including time spent writing posts). With my current hourly contracting/consulting rate, that's over $13,500 AUD worth of work done.
For what?
All this work for it to be more expensive, more complicated, less performant, and less reliable than if I just fucking used GitHub Pages or something.
Why?
Am I stupid? ;(
Epilogue
Also I made another status page. You can view it here. It's much nicer than my old one which I took down after like two months because I was getting sick of Fly.io's shit. This one is hosted on Render who I hope are going to be much less… bad. Otherwise I'll just rent another VPS and put Dokku or some shit like that on it.
I put very little effort into the page, which is why it doesn't save historical data, uses Tailwind CSS, and is written in Go. As much as I hate Go (and oh do I fucking hate that stupid fucking dweeby-ass gopher who could never be a REAL BRO like Ferris), it's probably the fastest and easiest way to get something like this up.