512KB Club
Chasing the mile high dub
Somewhere between refreshing Hacker News for the fiftieth time and injecting myself with copium by pretending this counts as 'keeping up with the industry', I found the 512KB Club: a directory of sites whose home pages stay under 512KB. It sounded exactly the kind of arbitrary constraint I get irrationally motivated by, so I decided to join.
Investigating Cloudflare's analytics, my blog was sitting at a surprisingly large 1.52MB of uncompressed assets. The goal was to get under 512KB without turning the blog into a lifeless HTML document, and permit entry to the hallowed institution.
1. Scrapping Big Brother (~550KB)
The first big win was analytics. I had a tiny PostHog client imported in _app.tsx, which pulled posthog-js into
every page just to send events to a new analytics platform I was testing. Removing that client dropped a fairly chunky
analytics SDK from the shared bundle, and now lets me live in blissful ignorance that anyone is actually reading my posts.
2. Slimmer article data on the Homepage (~400KB)
Next.js writes JSON for each statically generated route. Code heavy posts like my Rust borrow checker article produced ~200KB+ JSON blobs.
I split the data into two layers:
export const getPostMetadata = () => [
{ data: { title, date, description, readingTime, ... }, filePath }
];
export const getPosts = () => [
{ ...meta, content }
];Then I:
- Switched
/,/tech, and/miscto usegetPostMetadata(). - Added
prefetch={false}to articleLinks so their JSON doesn't prefetch.
The homepage now downloads just one small index.json file with metadata, not multiple full article payloads.
3. Replacing tiny libraries with code (~20KB)
dayjs was in the bundle solely to format dates as YYYY.MM.DD in a couple of places.
I replaced it with a small helper from Codex:
export const formatDate = (value: string | Date) => {
const d = new Date(value);
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
return `${y}.${m}.${day}`;
};4. Swapping JavaScript animation for CSS (~20KB)
The theme toggle used framer-motion to spin the sun/moon icon on tap. Fun library, but expensive for a single interaction.
Using Codex, it was rewritten as:
// TSX
<div className="theme-toggle flex items-center">
<HeaderIcon onClick={...}>
{theme === 'light' ? <RiMoonClearLine /> : <RiSunLine />}
</HeaderIcon>
</div>/* CSS */
.theme-toggle {
transition: transform 0.35s ease-out;
transform-origin: center;
will-change: transform;
}
.theme-toggle:active {
animation: theme-toggle-spin 0.35s ease-out;
}
@keyframes theme-toggle-spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}5. Miscellaneous tidy‑ups (~50–100KB)
A few smaller changes also helped:
- Making the bundle analyzer optional in
next.config.jsso production builds don't need@next/bundle-analyzerinstalled. - Avoiding extra MDX work on pages that don't use it, such as
rehype-shikicode highlighting.
Individually these were small, but together they knocked another chunk off the JS and config overhead.
Where I ended up
I started at roughly 1.52MB of bundle and shaved it down to around 424KB.
Nothing important went away:
- Dark mode still works, with the spinning theme toggle.
- MDX and syntax highlighting are still there.
- The home page still lists posts with dates, descriptions and reading times.
More importantly, you can now find philp.io on the 512KB Club!