Back to ArticlesEngineering

Modernizing My Web Stack: Porting from Gatsby to TanStack Start

A deep dive into migrating my personal blog from Gatsby to a full-stack, type-safe TanStack Start architecture.

MMichele Martone
July 5, 20264 min read
Modernizing My Web Stack: Porting from Gatsby to TanStack Start

Recently, I decided to modernize my personal website. The legacy version was built on Gatsby, which had served me well for years but was starting to show its age. I found myself unable to update the outdated Gatsby library dependencies anymore, and I wanted to move away from Gatsby's heavy GraphQL layers. I wanted less "magic" and more direct control over my code.

Additionally, I wanted a hands-on project to explore TanStack Start because we are planning to use it for an upcoming company hackday at Healthie. I wanted a modern framework that provided full-stack capabilities, high performance, and absolute type safety.

Since doing a full migration on my own can be a tedious process of debugging bundler splits and asset pipelines, I collaborated with Antigravity (Google DeepMind's agentic coding assistant) for a huge assist on the heavy lifting. Together, we moved the entire codebase over. Here is a detailed look at the migration process, the technical challenges faced, and the solutions implemented.


Why TanStack Start?

TanStack Start is a full-stack React framework built on top of Vite and TanStack Router. It provides:

  1. True Type Safety: File-based routing with fully typed route parameters, search queries, and loader data out-of-the-box.
  2. Vite Speed: Lightning-fast hot-module reloading (HMR) and compilation times compared to legacy Gatsby builders.
  3. Isomorphic Server Functions: Simple createServerFn endpoints that seamlessly handle server-side data fetching without needing to maintain separate API routes or GraphQL servers.

The Migration Playbook

1. Rebuilding the Data Loader Engine

The legacy Gatsby site loaded markdown files using GraphQL queries. To keep the new site fast and self-contained, I replaced the GraphQL engine with a Local Markdown Engine powered by Node's filesystem and gray-matter:

  • Server-Side Loading: Node's filesystem reads (fs and path) are isolated in articles.server.ts, ensuring they never bundle into the client.
  • Server Functions: I wrapped these loaders inside isomorphic endpoints in articles.ts using createServerFn. This allows client routes to fetch posts transparently during server-side rendering (SSR) and client navigation.

2. Relative Image Resolution

Markdown posts often contain relative image references (e.g. ![caption](image.jpg)). In Gatsby, these are parsed during the build. For TanStack Start, I implemented a double-sided solution:

  • Build/Dev Sync Script: Created scripts/copy-assets.js which automatically syncs all blog folders containing markdown and images from content/blog/ to public/blog/ during development and compile tasks.
  • Custom Markdown Renderer: Developed a custom MarkdownContent component. When rendering an image tag, it automatically rewrites relative links to their absolute static path:
    const resolvedSrc = src && !src.startsWith("http") && !src.startsWith("/")
      ? `/blog/${postSlug}/${src}`
      : src;
    

3. Ridding the Site of Duplicate Cover Images

By default, the homepage uses the first image found in a post's folder as its card thumbnail. However, rendering that same cover image at the top of the article page was redundant if the article already embedded it inline.

To fix this, I implemented a helper logic in the post detail view (posts.$slug.tsx) that checks if the cover filename exists inside the markdown body block. If it is already rendered inline, the top cover banner is skipped automatically.

4. Dynamic Categories & Kindle Highlights

Instead of hardcoding category lists, the Loader now dynamically compiles a list of unique categories directly from the parsed post frontmatters (such as Engineering, Career, Testing, and Reading).

Additionally, I ported my Kindle Highlights reader to load structured JSON files under content/books/ and render them in a clean quote layout at /books/$slug, utilizing TanStack Router's typed parameter loader.


The Outcome

The transition resulted in a vastly cleaner developer experience:

  • Build Speeds: Local dev server startup decreased from several seconds to under 300ms.
  • Unified Logic: Replaced complex Gatsby config boilerplate and GraphQL schemas with type-safe React routing and simple server functions.
  • Maintainability: Adding new blog posts or Kindle highlights now only requires editing markdown or JSON files under the content/ folder—the UI handles routing and asset sync automatically.