Skip to content
9 min readCase study

Four generations of my personal site, from Gatsby to Next.js

Four live URLs, one product: a Gatsby site with GraphQL and particles, a first Next.js build on v2, an editorial Next 16 site on v3, and the redesign now on the apex.

  • Gatsby
  • Next.js
  • MDX
  • Portfolio
  • SEO

Four generations of my personal site, from Gatsby to Next.js

I did not migrate once — I shipped four public generations of the same product. Each URL is a time capsule: Gatsby particles, an intermediate Next deploy on the v2 subdomain, the editorial stack (now on v3), and the current design on the apex domain.

Updated October 2026. I first wrote this when the editorial site (Gen 3) was on the apex. That design now lives at v3.danielastudillo.io, and danielastudillo.io runs a fourth generation: Next.js 16, an Apple-inspired redesign, a highlights page and a hardened contact form. The Gen 1–3 sections below are written as of June 2026. The older versions are still online, but search engines are told to skip them.

#TL;DR

I did not migrate once — I shipped four public generations of the same product (my portfolio + writing surface):

GenURLStack (high level)Repo / role
1v1.danielastudillo.ioGatsby 5, GraphQL data layer, React 18, Bootstrap, tsparticlespersonal-site — time capsule
2v2.danielastudillo.ioNext.js on Vercel (x-nextjs-prerender, App Router era)Intermediate Next rewrite — still live
3v3.danielastudillo.ioNext.js 16 canary, MDX blog, Tailwind v4, output: "standalone"personal-site-v2 (private repo, archive/v3 branch) — editorial site, the apex until October 2026
4danielastudillo.ioNext.js 16, MDX blog, Tailwind v4, Apple-inspired redesign, highlights pagepersonal-site-v2 (private repo) — active

Gens 2, 3 and 4 share a repo name (personal-site-v2, on different branches) but have different deploy targets and feature depth. “v2” and “v3” are now subdomains serving archived builds; the apex is the fourth generation.


#Generation 1 — Gatsby and GraphQL

When: portfolio-focused years, last refreshed May 2025 on the Gatsby starter lineage.

What it optimized for:

StrengthMechanism
Image pipelinegatsby-plugin-image, sharp transforms
Typography@fontsource/playfair-display serif hero
Motion@tsparticles/react landing
HostingStatic CDN on Vercel — zero backend

Pain that motivated Gen 2:

  • Every new MDX field wanted a GraphQL query or plugin touchpoint.
  • gatsby develop rebuild feel when iterating on essays.
  • Blog as “occasional posts” not a primary surface.

Gen 1 is not embarrassing — it is a measurable baseline for LCP, bundle, and build time.


#Generation 2 — first Next.js (v2 subdomain)

When: roughly May 2025 → early 2026 on v2.danielastudillo.io (still live; Vercel x-nextjs-prerender headers).

Timeline in personal-site-v2 (now a private repo):

WhenMilestone
Jul 2024Project scaffold
May 2025Next.js 15 + React 19 + Tailwind v4, dark mode, Font Awesome, projects/about pages
Jul 2025Contact form, animations, project imagery
Jul 2025First blog — blog route + MDX support (minimal compared to today)

What Gen 2 looked like vs Gen 1:

DimensionGen 1 (Gatsby)Gen 2 (v2 subdomain)
Data layerGraphQL nodesTypeScript data/*.ts
Visual identityParticles + BootstrapDifferent Tailwind v4 system, Helvetica-forward toggles
WritingRare postsMinimal blog (MDX landed July 2025, not the 20-post corpus)
Hero storyPlugin-era portfolioDark mode + font switcher + Framer-style polish

I kept v1 on its subdomain so “before / after” is a real diff. Gen 2 proved Next could own production DNS before I invested in the editorial site on the apex domain.


#Generation 3 — editorial Next

When: June 2026 — the apex domain danielastudillo.io picked up the full editorial revamp plus ongoing MDX expansion (20 posts, reading guide, case-study links). It stayed there until October, when I moved it to v3.danielastudillo.io to make room for Gen 4.

DNS: Nothing “broke” in the migration so far — it is too early to claim SEO or CWV wins. The operational work was adding CNAME records in Namecheap for v1, v2 and the apex (plus v3 once the editorial site moved there), each pointing at the Vercel DNS targets so all three hostnames stay live for comparison. Gen 2-specific UI on the subdomain is gone from main (overwritten); only the URL snapshot remains.

Stack highlights (Gen 3):

ConcernImplementation
Contentapp/blog/content/**/content.mdx — no GraphQL
Renderingforce-static where possible; Turbopack dev
SEOApp Router metadata / generateMetadata, sitemap.ts, structured data
FontsSelf-hosted /fonts, FontProvider, documented anti-flicker approach
Deployoutput: "standalone" + postbuild asset copy for Docker/Vercel
Projectsdata/projects.ts links to blogSlug case studies

Visual / UX shift from Gen 1:

  • v1: portfolio classic — particles, Bootstrap grid, GraphQL project cards.
  • v3: editorial — topic filters, series navigation (BlogReadingGuide), restrained motion, long-form depth for senior loops.

Gen 3 is not “Gen 2 with more CSS.” It is a content product decision: essays are first-class, projects point to narratives, and performance budgets are explicit (image formats, font cache headers, bundle splitting).


#Transition map (engineering view)

Text
Gen 1  Gatsby 5 + GraphQL + particles
         │  pain: essay velocity, GraphQL tax, build feel
         ▼
Gen 2  Next.js @ v2.danielastudillo.io
         │  pain: (fill in) — likely design system + blog scale + CWV targets
         ▼
Gen 3  Next 16 @ danielastudillo.io + MDX + standalone + 20 posts
         │  moved to v3 in October 2026
         ▼
Gen 4  Next 16 @ danielastudillo.io + Apple-inspired redesign
Migration stepChecklist item
1 → 2Export markdown/MDX; replicate routes; redirect old blog slugs
1 → 2Replace gatsby-plugin-image with next/image + explicit sizes
2 → 3Consolidate content folder convention; wire projects ↔ blog slugs
2 → 3Harden metadata, sitemap, structured data as code
All gensKeep prior subdomain alive for comparison

#Chrome UX Report — field data (June 2026)

PageSpeed Insights surfaces 28-day CrUX p75 metrics under “Discover what your real users are experiencing.” That is not the same as Lighthouse lab scores — it needs enough real Chrome traffic on the exact origin.

OriginField data (mobile, PSI)
danielastudillo.ioNo Data (report, Jun 2 2026)
v1.danielastudillo.ioNo Data (low-traffic subdomain; separate CrUX origin)
v2.danielastudillo.ioNo Data (same — comparison subdomain, not enough volume)

That is expected for a personal portfolio: CrUX omits origins below the privacy threshold. Until field data appears, treat Lighthouse lab and RUM you own (Vercel Web Analytics, optional web-vitals beacon) as the honest signals. Re-check PSI after the apex migration has had a full CrUX collection window.


#Lighthouse performance (June 2026, mobile, lab)

Headless Chrome, Lighthouse 12.6 — homepage / (same day, three sequential runs). The third row is the editorial site, which was still on the apex when I ran this and now lives at v3:

URLPerformanceLCPFCPTBTCLSSpeed Index
v1.danielastudillo.io873.0 s2.8 s0 ms0.0024.7 s
v2.danielastudillo.io913.6 s1.0 s30 ms0.0001.2 s
v3.danielastudillo.io923.0 s1.5 s80 ms0.0003.8 s

Cold curl on the same day showed Gen 1 HTML at ~237 KB vs ~88 KB for both Next URLs — particles and Gatsby payload explain that gap more than the Performance score spread.

Read: Lab scores cluster high-80s/low-90s with near-zero CLS. Gen 1’s Speed Index (4.7 s) and FCP (2.8 s) lag Gen 2 — consistent with tsparticles + heavier first paint. Gen 2’s LCP (3.6 s) is the slowest LCP despite the best FCP/TBT — hero image weight on that deploy. The v3 home’s Speed Index (3.8 s) reflects a denser editorial layout, not a failed migration.


#Gen 3 performance budget (repo defaults)

Gen 3’s next.config.ts made choices that did not exist on Gatsby v1:

KnobSettingWhy
ImagesWebP + AVIF, 1-year minimumCacheTTLCDN-friendly hero and OG assets
optimizePackageImportsFramer Motion, Font Awesome, next-themes, ZodSmaller client graphs without manual barrel surgery
Webpack splitChunksDedicated chunks for React, Framer, FACache isolation on repeat visits
output: "standalone"Docker/Vercel postbuild copySame artifact locally and in CI
Webpack context lockprocess.cwd() onlyPrevents monorepo parent package.json resolution bugs

Gen 2 on the subdomain inherited an earlier slice of this file; Gen 3 is where blog scale (20 MDX posts, reading guide, project ↔ essay links) met explicit bundle and font-cache policy.

MetricWhy it still matters
gatsby build vs next build wall timeCost of publishing
Total JS on /Particles vs editorial home
LCP on heroImage + font strategy
Time to add one MDX postFiles touched, commands run

#Closing thought

Three URLs beat three README bullets: Gatsby proved static images and GraphQL pipelines; the v2 subdomain proved Next could own DNS; the editorial site proved long-form MDX and case-study linking are the product, and Gen 4 keeps that foundation under a new design. Name generations by URL + stack, not by repo folder names alone.


#Reader field guide

When to keep prior deploys alive: You are comparing stack generations; recruiters ask for before/after; SEO and CWV need separate origins measured honestly.

Operational checklist

  • Map each generation to URL + stack, not folder name alone (v2 subdomain ≠ current apex deploy)
  • Replicate routes and slug redirects when moving MDX between frameworks
  • Replace gatsby-plugin-image with next/image + explicit sizes during Gatsby → Next moves
  • Export metadata / generateMetadata, sitemap.ts, and structured data as code in Gen 3
  • Lab Lighthouse on / after each major visual change — CrUX may stay “No Data” on low-traffic personal domains
  • Track wall-clock publish cost (gatsby build vs next build) and JS weight on hero, not vibes
  • Wire data/projects.ts → blogSlug so projects point at case-study essays

Lab scores to compare (June 2026 mobile homepage): v1 87, v2 91, v3 92 — see Lighthouse table above for LCP/FCP/CLS detail.


#On this site

PostWhy
Building a CRDT collaborative editorShipped product work linked from the current generation
Astro + Bun landing template experimentStatic SKU vs this MDX editorial stack
Triaging Shai-Hulud on my portfolio v2Supply-chain response on personal-site-v2 — audit vs worm IoCs

#References (curated)

Three live URLs are the migration proof; Next and Gatsby docs are for readers planning their own cutover.

ReferenceNotes
Gen 1 — GatsbyGraphQL image pipelines and particles hero (Lighthouse 87 in lab).
Gen 2 — Next subdomainFirst App Router deploy on Vercel (91 in lab).
Gen 3 — v3 archiveMDX blog + standalone output (92 in lab, May 2026); the apex until October 2026.
Gen 4 — apexCurrent site: Apple-inspired redesign, highlights page, hardened contact form.
Next.js App RouterFile-based routes, RSC defaults, and metadata APIs we use now.
Next.js — MigratingApp Router migration guide when you outgrow build-time GraphQL for everything.
Core Web VitalsLab scores are directional—field data on a personal origin is often sparse.