Migrating Dark Horse Charters from Gatsby to TanStack Start

Migrating Dark Horse Charters from Gatsby to TanStack Start

May 01, 20264 min read

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:

MetricGatsby 3TanStack StartDelta
Performance Score7893+15 ๐ŸŸข
Accessibility94100+6 ๐ŸŸข
FCP0.5s0.5ssame
LCP4.4s0.5s-3.9s ๐ŸŸข
Total Blocking Time50ms210ms+160ms ๐Ÿ”ด
CLS00.039slight regression
Speed Index0.6s1.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

  1. 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.
  2. 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.
  3. 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. ๐ŸŽฃ