How to McMaster-Carr Your Website: Adventures in Instant Gratification
If you’ve ever ordered industrial supplies, you know about McMaster-Carr. If you haven’t, go visit mcmaster.com right now. I’ll wait.
Did you notice? Of course you didn’t notice, because there was nothing to notice. The page just… appeared. No loading spinner. No content jumping around. No waiting for JavaScript to figure out what a button is. It’s so fast that your brain interprets it as “already loaded” rather than “loading quickly.”
McMaster-Carr’s website is the platonic ideal of web performance, and I’ve spent the last few weeks trying to figure out how to steal their magic for my little personal blog.1
The Problem With Modern Web Development
My site was built with Gatsby, which is a perfectly fine framework that does perfectly reasonable things. It also shipped 1.7 MB of JavaScript to render what is essentially a bunch of text with some links. Every page load involved downloading React, hydrating the entire DOM, and then… displaying static content that hadn’t changed since I wrote it.
This is the state of modern web development. We’ve collectively decided that a blog post needs a JavaScript runtime, and we’ve normalized multi-second load times for content that could be served as HTML from a filing cabinet.
Time to Interactive on my homepage was 2.3 seconds. For text. Text.
What McMaster-Carr Does Differently
McMaster-Carr’s secret isn’t really a secret at all. They ship HTML. When you request a page, you get the page. The browser knows what to do with HTML—it’s been doing it since 1991. No hydration, no framework initialization, no bundle splitting strategy meetings.
Of course, McMaster-Carr also has decades of institutional knowledge about performance optimization, a team of engineers who probably dream in milliseconds, and infrastructure I can only imagine. But the core principle is simple: send the user what they asked for, in a format the browser already understands.
The Migration
I moved from Gatsby to Astro, which embraces something called the “Islands Architecture.” The idea is that most of your page is static HTML, and you only add JavaScript for the specific components that actually need interactivity.
A blog post doesn’t need JavaScript.2 Neither does the homepage, or the posts list, or really most pages on a personal website. What does need JavaScript? The interactive tools like the Vim reference and the Work Cycle Clock. Those get JavaScript. Everything else gets good old-fashioned HTML.
Here’s what a page component looks like in Astro:
---
// This runs at build time, not in the browser
import Layout from '../components/Layout.astro';
const posts = await getCollection('blog');
---
<Layout title="Posts">
<ul>
{posts.map(post => (
<li><a href={`/posts/${post.slug}/`}>{post.data.title}</a></li>
))}
</ul>
</Layout>
That’s it. No useState, no useEffect, no hydration. The server generates HTML, the browser displays HTML, everyone goes home happy.
For the tools that actually need client-side behavior, you add a directive:
<VimReference client:load />
That component gets hydrated with React. Just that component. The rest of the page remains blissfully JavaScript-free.
The Results
After the migration:
| Metric | Before (Gatsby) | After (Astro) |
|---|---|---|
| Time to Interactive | 2.3s | 0.9s |
| Total JavaScript | 1.7 MB | 372 KB |
| Total Site Size | 7.3 MB | 1.6 MB |
| Lighthouse Performance | 100 | 100 |
The Lighthouse score stayed at 100, which is good because it means I didn’t break anything, but also somewhat unsatisfying because it means the “before” wasn’t being properly penalized for shipping a megabyte of JavaScript to display text.
The real difference is in the feel. The site now loads the way McMaster-Carr loads—not “fast” but “already there.” When you click a link, the page appears. That’s it. That’s the whole experience.
The Deeper Lesson
There’s something philosophically interesting about this migration. For years, we’ve been adding complexity to solve problems that the complexity itself created. React’s hydration exists because React needs to take over the DOM. Bundle splitting exists because bundles got too big. Code splitting exists because we’re shipping too much code.
McMaster-Carr sidesteps all of this by asking a different question: what if we just… didn’t? What if we sent HTML and let the browser do what browsers do?
I’m not suggesting everyone abandon their React apps. If you’re building a complex interactive application, you need the tools to manage that complexity. But for a blog? For documentation? For content that changes once when you write it and never again?
Maybe we should just send the HTML.3
Postscript: The Tailwind Incident
In a fitting coda to this performance saga, I spent two hours today debugging why my navigation wasn’t appearing on the site. Turns out there were two Tailwind config files—one with the correct content paths, one without. Tailwind was reading the wrong one and not generating any of the responsive CSS classes.
The fix was deleting a file.
Sometimes the fastest code is the code you don’t run, and sometimes the best debugging is realizing you have too many config files.
Footnotes
- For a site that gets maybe 200 visitors a month, most of whom are probably me checking if my latest deploy worked. ↩
- Unless you’re doing something weird with your blog posts, in which case, I’m intrigued and would like to subscribe to your newsletter. ↩
- This is, I realize, a take that would have been completely unremarkable in 2005. We’ve come full circle, except now we have build tools. ↩