Skip to content
··10 min read

What Happens After You Migrate: 8 Days of Compounding Returns

I migrated my blog to Next.js and thought the hard part was over. Then the compounding started — 6 mega guides, a smarter AI assistant, native newsletter, bot protection, and SEO overhaul in 8 days.

Eight days ago, I rebuilt my entire blog backend in 4 days. I migrated 485 WordPress posts to Next.js, brought Sydney (my AI chatbot) back to life, and shipped a production site.

I thought that was the story. Migration done, celebrate, move on.

I was wrong. The migration wasn't the destination — it was the starting line.

Here's what my site looked like on Feb 5 vs today:

FeatureFeb 5 (Migration Day)Feb 13 (Today)
Blog posts485 (migrated from WordPress)492 (6 new mega guides)
Sydney AIBasic RAG, untested qualityEvaluated, optimized, 81% hit rate
NewsletterBeehiiv embed (third-party)Native Supabase + Resend, interest-based
SEOBasic sitemap + meta tagsStructured data, FAQ schema, llms.txt, AEO
SecurityRate limiting onlyCloudflare Turnstile + rate limiting
PerformanceBasic Lighthouse checksSystematic profiling with Chrome DevTools MCP
Featured imagesManual creationAI-generated pipeline (Gemini + auto-optimize)

Every one of those improvements happened because the previous one made it easier. That's the compounding I didn't expect.


The Unlock: Your Blog Is Now Code

Here's the thing nobody tells you about migrating from WordPress to a code-based stack: the migration itself isn't the point. The point is what becomes possible after.

When your blog is 492 MDX files in a Git repo instead of rows in a MySQL database, everything changes:

  • Every post is a file — Claude Code can read, search, and modify them at scale
  • Every change is a diff — you can review exactly what changed, across 50 posts at once
  • Every feature is composable — your newsletter system can read the same post metadata as your AI chatbot
  • Every deployment is a commandpnpm build && vercel --prod, done

With WordPress, updating 12 blog posts meant logging into wp-admin, clicking through each one, making changes, updating, repeat. With MDX files and Claude Code? I could say "add a link to the national parks pillar post in all 12 existing park guides" and it would read each file, add the right cross-link in the right context, and show me the diff. All 12 updates in minutes.

This is the unlock. Not the migration. The velocity that comes after.


Six Mega Guides in 4 Days

The first thing I built after migration? Content. A lot of it.

I'd been wanting to write comprehensive expat guides for months — the kind of 2,000-4,000 word pillar posts that actually help people navigating the chaos of moving to the US. WordPress made that painful. Each post required manual formatting, image uploads, SEO plugin configuration, category management.

Now? The pipeline looks like this:

  1. Brainstorm the post structure and outline
  2. Write the full MDX with SEO/AEO optimization baked in
  3. Generate featured image — Claude reads the post, writes a prompt, sends it to Gemini to generate the image
  4. Optimize — Python script converts to WebP, compresses for web
  5. Upload to Vercel Blob
  6. Publishgit push, vercel --prod, pnpm db:publish
  7. Sydney knows about it immediately — the post is synced to Supabase with embeddings

Here's what shipped in those 4 days:

PostTopicWords
Healthcare GuideHSA, FSA & HDHP explained~3,200
Credit BuildingZero to 720+ credit score~3,500
Savings & InvestingT-Bills vs HYSA breakdown~2,800
Credit Card RewardsPoints, miles, cashback strategy~3,000
Relocation GuideFull moving-to-US playbook~3,800
National Parks26 parks, 4 road trips~4,200

Each post follows the same AEO pattern: answer-first openings, comparison tables, 120-180 word sections, bold definitions, FAQ schema at the bottom. The kind of structure that gets 340% more AI citations.

The national parks guide was the biggest — a pillar post linking back to 12 existing park reviews. I also built a PhotoGallery component for it, because scrolling through 20 individual images was brutal. Now it's a clean grid with a lightbox. Simple, yet effective.


Sydney Got Smarter

When I first brought Sydney back during the migration, she worked — and I tested her manually before launch. She could answer questions, find relevant posts, cite sources. But manual testing only tells you "this seems okay." It doesn't tell you how okay, or where the gaps are, or how to make it better.

I had no systematic way to measure quality — and without measurement, I had no way to improve.

So I built a RAG evaluation framework. 32 test queries across 12 categories, each with expected results that I hand-curated. Questions like "What credit cards should expats get?" should return the credit building guide. "Tell me about Yosemite" should return the Yosemite trip report.

Then I tested 30 different configuration combinations — 6 similarity thresholds × 5 result counts — and measured hit rate, precision, and temporal diversity.

The results were eye-opening:

MetricBefore (Default)After (Optimized)
Hit rate~30%81.2%
Zero-hit queries6 out of 300 out of 30
Temporal spread1.2 years6.7 years

The biggest fix was embarrassing: the OpenAI SDK was silently dropping the dimensions parameter when running under Next.js. Sydney was generating 1536-dimension embeddings but searching a 384-dimension index. No wonder results were bad. Switching to direct fetch() calls fixed it overnight.

I also expanded Sydney's system prompt to cover all 490+ blog topics — she now knows about national parks, credit cards, healthcare, and the full expat content library. Not just AI and marketing.

Two commands to test it yourself:

pnpm eval:rag        # Test against local Supabase
pnpm eval:rag:prod   # Test against production

Now I can actually measure when Sydney gets smarter. That's the kind of infrastructure you never build when you're running WordPress.


SEO and AEO at Scale

SEO in 2026 isn't just about Google anymore. It's about being cited by ChatGPT, Perplexity, and Claude when someone asks a question your content answers.

I implemented a full SEO/AEO stack in a single weekend:

For traditional search:

  • Dynamic sitemap (639 pages indexed)
  • Structured data — Person, WebSite, Article, BreadcrumbList, FAQPage schemas
  • RSS feed for syndication
  • Google Search Console verified

For AI engines (AEO/GEO):

  • llms.txt — a priority content guide for AI crawlers (GPTBot, Claude-Web, PerplexityBot all allowed in robots.txt)
  • Answer-first pattern in every section opening (bold key statement in the first 150 words)
  • Question-format H2s that match how people ask AI assistants
  • FAQ sections auto-detected and rendered as FAQPage schema

For performance: I used Chrome DevTools MCP to profile different page types — homepage, blog listing, individual posts, the /ask page — and identified bottlenecks. Being able to run a performance trace, analyze the results, and fix the issue in the same Claude Code session is... unreasonably efficient.

The result? Every new post I write automatically gets structured data, FAQ schema (if it has a FAQ section), and is formatted for AI extraction. No plugins. No manual configuration. It's just how the site works now.


Beehiiv Out, Native Newsletter In

This one surprised me. I had a working newsletter on Beehiiv. It collected emails. It sent updates. Why replace it?

Three reasons:

  1. Control — I wanted interest-based subscriptions. "AI & Technology" subscribers shouldn't get national parks content. Beehiiv's free tier didn't support that.
  2. Integration — My subscriber data now lives in the same Supabase database as Sydney's search index. One source of truth.
  3. Cost — Supabase (free tier) + Resend (free tier for low volume) = $0/month.

The system I built:

  • Double opt-in — subscribe → verification email → confirmed → welcome email with personalized recommendations
  • 5 interest groups — AI, Expat Life, Leadership, Marketing, Travel & National Parks
  • Smart matching — post categories auto-map to subscriber interests. When I publish a credit card guide, only "Expat Life" subscribers get notified.
  • Daily cron — Vercel runs a job at 11 AM PST, finds posts from the last 48 hours, sends targeted notifications
  • Deduplicationnotification_log table prevents duplicate emails, ever

The best part? The subscribe form at the bottom of every blog post auto-selects the relevant interest based on the post's category. Reading an AI article? The "AI & Technology" pill is pre-selected when you scroll down to subscribe.

Building this from scratch sounds like a lot of work. It was about an evening of focused effort. The Supabase migration, API routes, email templates, and cron job — Claude Code handled the scaffolding while I focused on the logic and copy.


Security: Invisible Until You Need It

With a public-facing AI chatbot and a newsletter signup, I had two endpoints that bots love to abuse. Rate limiting was already in place (Upstash Redis, 5 requests per minute), but I wanted a second layer.

Enter Cloudflare Turnstile — an invisible CAPTCHA that only shows a challenge if it suspects suspicious activity. For 99% of real visitors, it's completely invisible. For bots? It's a wall.

I added it to the /ask endpoint first (Sydney chat), then reused the exact same pattern for /api/subscribe. Same verification library, same graceful degradation:

  • No environment variables configured → bypass (local development works without it)
  • Cloudflare API is down → fail-open (rate limiter is the fallback)
  • Token missing but secret key is set → reject (catches bots bypassing the frontend)

The reuse is what makes this a compounding story. The Turnstile integration for the newsletter took minutes, not hours, because the infrastructure was already there from Sydney.


The Compounding Effect

Here's what I didn't plan but happened naturally:

Migration to Next.js (Feb 1-5)
  └─→ MDX files enable Claude Code to read/modify content at scale
       └─→ 6 mega guides written with SEO/AEO optimization (Feb 6-11)
            └─→ New content exposes Sydney's search quality issues
                 └─→ RAG eval framework built, Sydney optimized (Feb 11)
                      └─→ More content needs a newsletter (not Beehiiv)
                           └─→ Native newsletter built on Supabase (Feb 12)
                                └─→ Public endpoints need bot protection
                                     └─→ Turnstile added to Sydney + subscribe (Feb 12-13)

None of these were planned as a sequence. Each one revealed the next need. The migration made content creation fast. Fast content creation exposed search quality gaps. Fixing search made me want better distribution. Better distribution needed protection.

That's compounding. Each improvement doesn't just add value — it multiplies the value of everything that came before it.

And the common thread through all of it? Every piece of my site is now code that can be read, tested, and modified programmatically. That's the real unlock.


The Numbers

Because I know you're wondering:

MetricValue
Days since migration8
New blog posts7 (including this one)
New features shipped6 (newsletter, Turnstile ×2, RAG eval, SEO stack, PhotoGallery)
Blog posts updated12+ (cross-linking, broken link fixes)
Lines of code added~5,600
Database tables added2 (subscribers, notification_log)
API endpoints added4 (subscribe, verify, unsubscribe, cron)
Cost increase$0/month (all free tiers)

What's Next

I'm not done. The compounding hasn't stopped:

  • More expat guides — tax strategy, visa timelines, banking comparisons. The series has legs.
  • Sydney getting smarter — now that I can measure quality, I want to push hit rate past 90%.
  • More deep dives on building with AI — the tools are evolving weekly. I'm documenting as I go.

But honestly? I'm mostly just excited to keep writing. For the first time in 17 years of blogging, publishing a new post is genuinely fun — not a 45-minute WordPress chore.

If any of this sounds useful — expat life, AI tools, building products, or just following along as I figure this out — I built that newsletter system for exactly this reason. Pick the topics you care about, skip the rest. No noise, no spam, just the stuff that matters to you.

You can subscribe at the bottom of this page, or ask Sydney anything about the 492 posts already on the site. She's pretty good now — 81% hit rate and improving :P


Frequently Asked Questions

What is the compounding effect in website development?

The compounding effect is when each improvement to your site makes the next improvement faster and easier. For example, migrating to a code-based stack enabled AI-assisted content creation, which revealed search quality issues, which led to building an evaluation framework. Each step built on the previous one.

How do you optimize blog posts for AI search engines?

AI search engines prefer structured, answer-first content with clear headings and concise sections. Key techniques include: bold definitional statements in the first 150 words, question-format H2 headings, comparison tables, 120-180 word sections, and FAQ schemas. These patterns get up to 340% more AI citations.

What is a RAG evaluation framework?

A RAG evaluation framework tests how well your AI assistant finds and returns relevant content. It uses predefined queries with expected results (ground truth) and measures metrics like hit rate, precision, and temporal diversity. This lets you systematically optimize your search settings instead of guessing.

Why replace Beehiiv with a custom newsletter system?

A custom newsletter system gives you full control over subscriber data, interest-based targeting, and integration with your existing database. By building on Supabase + Resend, I got interest-based subscriptions, automated notifications, and $0/month hosting — all integrated with Sydney's search index.

How does Cloudflare Turnstile differ from traditional CAPTCHAs?

Cloudflare Turnstile is an invisible bot detection system that only shows challenges to suspicious visitors. Unlike traditional CAPTCHAs that make every user solve puzzles, Turnstile runs silently for legitimate users. It uses browser signals to detect bots without interrupting the user experience.


Still coding, still learning, still watching the compound interest grow.

Continue Reading

My Journey
Connect
Preferences