8 min readCase study

Three generations of my personal site: Gatsby, Next, and Next again

Not two sites — three live URLs: Gatsby + GraphQL + particles, an intermediate Next deploy on the v2 subdomain, then the current editorial Next 16 stack with MDX and standalone output.

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

Three generations of my personal site: Gatsby, Next, and Next again

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

#TL;DR

I did not migrate once — I shipped three 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
3danielastudillo.ioNext.js 16 canary, MDX blog, Tailwind v4, output: "standalone"personal-site-v2 — active

Gen 2 and Gen 3 share a repo name (personal-site-v2) but different deploy targets and feature depth. Treating “v2” as only the subdomain and “v3” as the apex domain matches how the URLs actually behave today.


#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:

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 (apex domain)

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).

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, 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 (current repo):

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
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):

URLPerformanceLCPFCPTBTCLSSpeed Index
v1.danielastudillo.io873.0 s2.8 s0 ms0.0024.7 s
v2.danielastudillo.io913.6 s1.0 s30 ms0.0001.2 s
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 apex home’s Speed Index (3.8 s) reflects a denser editorial layout, not a failed migration.


#Gen 3 performance budget (repo defaults)

The current next.config.ts encodes 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 apex site proved long-form MDX and case-study linking are the product. 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.tsblogSlug so projects point at case-study essays

Lab scores to compare (June 2026 mobile homepage): v1 87, v2 91, apex 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 — apexMDX blog + standalone output (92 in lab, May 2026).
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.