
Migrating Dark Horse Charters from Gatsby to TanStack Start
The Setup
Dark Horse Charters has been running on Gatsby 3 for a few years now. It has been great, but Gatsby 3 is showing its age. The same ecosystem stagnation I wrote about when I migrated my personal site to Next.js applies here too. Plugin rot, outdated docs, a GraphQL data layer that's serious overkill for a 5-page fishing charter site.
This time instead of reaching for Next.js again, I rebuilt it with TanStack Start, Tanner Linsley's new full-stack React framework built on TanStack Router. Before and after merging the PR, I took pagespeed insights and lighthouse audits of both sites to see how they compared.
The Numbers (Desktop)
I ran both through PageSpeed Insights on the same day. Here's the desktop comparison:
| Metric | Gatsby 3 | TanStack Start | Delta |
|---|---|---|---|
| Performance Score | 78 | 93 | +15 ๐ข |
| Accessibility | 94 | 100 | +6 ๐ข |
| FCP | 0.5s | 0.5s | same |
| LCP | 4.4s | 0.5s | -3.9s ๐ข |
| Total Blocking Time | 50ms | 210ms | +160ms ๐ด |
| CLS | 0 | 0.039 | slight regression |
| Speed Index | 0.6s | 1.0s | +0.4s |
The LCP improvement is the headline - 4.4s down to 0.5s on desktop. That's huge. Gatsby was shipping the hero image without proper preloading, which meant the browser was discovering it late in the waterfall. TanStack Start's setup gets that image in front of the browser immediately.
Mobile scores are a work in progress - I'll do a follow-up post once I've addressed the image delivery recommendations PSI is flagging.
The Big Winners: Build Time and Bundle Size
Honestly these numbers are what I'm most excited about.
Build time: Gatsby was averaging over 5 minutes per build. TanStack Start is down to 16 seconds, a 95%+ reduction. When you're iterating on a client site, waiting 5 minutes to see a change is genuinely painful.
Bundle size: Gatsby's output was sitting at 99.6MB. TanStack Start came in at 16.7MB, a 83% reduction. Gatsby's asset pipeline and plugin ecosystem had clearly been accumulating weight over the years without me noticing.
What I Noticed
The LCP win comes down to image handling. Gatsby was using its own image plugin with inline base64 placeholders, but the LCP image wasn't being prioritized correctly. The new build handles that properly.
The slight TBT regression (50ms โ 210ms) on desktop is worth watching. TanStack Start's client-side hydration and router bootstrapping adds a bit more main-thread work than Gatsby's static shell. It's still in the green zone but something to keep an eye on.
CLS went from a perfect 0 to 0.039, still passing Core Web Vitals thresholds, but I need to track down what's shifting. Most likely an image without explicit dimensions somewhere.
Lessons Learned
- LCP is almost always an image problem. Both before and after, the recommendations come back to image delivery. It's the highest-leverage optimization on a content-heavy site like this.
- Gatsby's build overhead compounds over time. 99.6MB and 5-minute builds didn't happen overnight, they crept up as the project aged. Starting fresh exposed how much had accumulated.
- TanStack Start is legitimately production-ready. The DX is great, the file-based routing is clean, and without a GraphQL layer to manage the codebase is much easier to reason about.
The site is faster where it counts, the deploy pipeline is dramatically snappier, and I'm not fighting a plugin ecosystem to keep things running.
Next up: fixing that mobile LCP. ๐ฃ