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.

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:
- True Type Safety: File-based routing with fully typed route parameters, search queries, and loader data out-of-the-box.
- Vite Speed: Lightning-fast hot-module reloading (HMR) and compilation times compared to legacy Gatsby builders.
- Isomorphic Server Functions: Simple
createServerFnendpoints 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 (
fsandpath) are isolated inarticles.server.ts, ensuring they never bundle into the client. - Server Functions: I wrapped these loaders inside isomorphic endpoints in
articles.tsusingcreateServerFn. 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. ). 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.jswhich automatically syncs all blog folders containing markdown and images fromcontent/blog/topublic/blog/during development and compile tasks. - Custom Markdown Renderer: Developed a custom
MarkdownContentcomponent. 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.