Every headless WordPress conversation starts the same way — someone draws an architecture diagram with arrows pointing from a REST API to a shiny Next.js frontend, and it looks clean. Too clean.
This is a post about what happens when you close the whiteboard and open the actual codebase.
The Stack Decision: GraphQL vs. REST vs. Direct MySQL
This is usually the first fork in the road. For this build, the client already had a well-indexed WooCommerce site. The product catalog, slugs, and taxonomy structure were already doing heavy SEO work. So the constraint was simple: nothing about the data layer changes, only how we consume it.
WPGraphQL was a real option — but it meant adding a plugin dependency to a WordPress install we were actively trying to slim down. The WP REST API was already there, no installation required, and exposed exactly what we needed: products, categories, pages, and media — all queryable by slug.
The decision: WP REST API, consumed server-side via Next.js fetch in Server Components.
// Fetching a product by slug — preserving the existing URL structure
const res = await fetch(
`${process.env.WP_API_BASE}/wp/v2/product?slug=${params.slug}&_embed`,
{ next: { revalidate: 3600 } }
);
const [product] = await res.json();
No new dependencies on the WordPress side. The legacy install runs as a lean shell — no active theme, minimal plugins, just the REST API and the data.
The Site Kit Problem: Bridging Familiar Workflows
This is where most migrations quietly fail the client.
The previous team lived inside WordPress admin. Google Site Kit gave them traffic stats, Search Console data, and Analytics — all surfaced in a UI they knew. Ripping that away and telling them "just use Google Analytics directly" is a workflow regression, not an upgrade.
The pivot here was building a lightweight admin dashboard as part of the Next.js project — not a full replacement for Site Kit, but a mirror of the metrics they actually checked daily:
- Page views (via GA4 Data API)
- Top landing pages
- Recent orders pulled from the WooCommerce REST API
The goal wasn't feature parity. It was behavioral parity — the client opens one tab and sees what they used to see. The underlying data source is more direct, but the cognitive load is the same or lower.
The client adopted the dashboard faster than expected. What used to require navigating between WordPress admin, Site Kit, and WooCommerce reports now lives in one place. The feedback has been straightforwardly positive.
Minimizing Technical Debt for the Existing Team
The client's team weren't Next.js developers. That's a real constraint, not a footnote.
Three decisions kept debt low:
1. Content stays in WordPress. No CMS migration, no content retraining. They still log into /wp-admin to add products and write posts. The frontend just renders what the API returns.
2. ISR over SSR where possible. Incremental Static Regeneration means most product pages rebuild on a schedule, not on every request. The team doesn't need to think about cache invalidation — publishing in WP triggers a revalidation hook via a lightweight custom WP action that hits a Next.js API route.
3. Deployment is abstracted. The client's team doesn't push code. FastPanel hosts the WordPress shell. Railway handles the Next.js deployment. Git push to main → production. The ops surface they touch is zero.
Shipping to Production
The handoff checklist that actually mattered:
✅ All existing product slugs verified against the new dynamic routes — [slug].tsx maps 1:1 with WP slugs, no redirects needed
✅ sitemap.xml generated dynamically from the REST API, submitted fresh to Search Console
✅ robots.txt scoped correctly — Next.js app routes indexed, /wp-admin and API endpoints excluded
✅ Environment variables separated — WP_API_BASE on Railway, no credentials in the repo
✅ Smoke test on mobile: LCP, CLS, INP checked before DNS cutover
What's Next: Winning the Last 2%
The next phase is product workflow migration — bringing product creation, variant management, and inventory updates into the dashboard directly, reducing reliance on /wp-admin to near zero. The WordPress shell stays as the data layer; the goal is making it invisible to the day-to-day operator.
Getting a client from "I'll still pop into WP admin occasionally" to "I haven't opened WP admin this week" is the real UX win.
That's what 98% workflow migration looks like in practice — and it's what the next post in this series covers.
If you're evaluating this move for a client — especially one on WooCommerce — the questions worth asking first aren't about the stack. They're about workflows: what does the team open every morning, and how do you make sure that still works on day one after go-live?
Top comments (0)