Skip to content
Zayne Lovecraft
All posts

Technical SEO · · 2 min read · Updated

Generative Engine Optimization (GEO) & CWV Gates in Next.js

How to audit Next.js apps for AI search citations (JSON-LD entity graphs, llms.txt, bot rules) and enforce Core Web Vitals client bundle budgets in CI.

Zayne Lovecraft

Software Engineer III

Close-up of a digital interface showcasing futuristic graphs and data analytics in low light.
Photo by Egor Komarov on Pexels

Ranking in traditional search and getting cited by AI answer engines (ChatGPT Search, Claude, Perplexity) come down to the same discipline: clean semantic HTML, machine-readable entity graphs, and fast page loads. Yet most teams only discover a broken schema block or a bloated client bundle after traffic drops.

To catch both classes of regressions early, I open-sourced two zero-dependency CLIs: geo-audit (case study) for Technical SEO + Generative Engine Optimization (GEO), and next-vitals-guard for Next.js client bundle budgets.

1. Auditing AI search readiness with geo-audit

Traditional SEO linters check whether you have a <title> tag and a <meta name="description">. That's table stakes, but AI crawlers and RAG pipelines care just as much about how unambiguously your page declares its entities and whether your site exposes a machine-readable map.

Running geo-audit against a live URL scores 8 signals out of 100:

$ npx github:zaynelovecraft/geo-audit https://www.zaynelovecraft.com

GEO & Technical SEO Audit — https://www.zaynelovecraft.com/

  ✓ [10/10] Title tag (15-65 chars) — "Zayne Lovecraft | Full-Stack Engineer, AI & Technical SEO" (61c)
  ✓ [10/10] Meta description (50-165 chars) — 145 chars
  ✓ [10/10] Canonical link tag — https://www.zaynelovecraft.com
  ✓ [15/15] Single semantic <h1> — Found 1 <h1> tag(s)
  ✓ [10/10] Semantic HTML5 landmarks (<main>, <article>) — main=true, article/section=true
  ✓ [20/20] JSON-LD structured data — Types: Person, WebSite
  ✓ [15/15] robots.txt allows AI citation crawlers — GPTBot / ClaudeBot / PerplexityBot allowed
  ✓ [10/10] /llms.txt machine-readable index — Present

Adding a dynamic /llms.txt route in Next.js App Router

When I first ran geo-audit against this portfolio, it scored 90/100 because /llms.txt was missing. Adding a dynamic route handler in src/app/llms.txt/route.ts fixed the gap in 30 lines of TypeScript and keeps the file automatically in sync with new blog posts and case studies:

// src/app/llms.txt/route.ts
import { getAllPosts } from "@/lib/posts";
import { OPEN_SOURCE_REPOS, PROJECTS, SITE_CONFIG } from "@/lib/site-data";

export function GET() {
  const posts = getAllPosts();
  const body = [
    `# ${SITE_CONFIG.name}`,
    `> ${SITE_CONFIG.summary}`,
    "",
    "## Case Studies",
    ...PROJECTS.map((p) => `- [${p.name}](${SITE_CONFIG.url}/work/${p.slug}): ${p.summary}`),
    "",
    "## Open Source",
    ...OPEN_SOURCE_REPOS.map((r) => `- [${r.name}](${r.url}): ${r.description}`),
    "",
    "## Articles",
    ...posts.map((p) => `- [${p.title}](${SITE_CONFIG.url}/articles/${p.slug}): ${p.description}`),
  ].join("\n");

  return new Response(body, {
    headers: { "Content-Type": "text/plain; charset=utf-8" },
  });
}

2. Catching "use client" bundle bloat with next-vitals-guard

As I covered in Understanding Client Components and Client Boundaries in Next.js and Engineering 100 Lighthouse Scores, a single misplaced "use client" directive can pull a heavy library into your shared layout chunk and tank Interaction to Next Paint (INP) overnight.

next-vitals-guard walks .next/static/chunks right after next build, computes the exact zlib.gzipSync transfer size of every .js file, and exits non-zero if any single chunk or total client payload crosses your threshold:

$ npx github:zaynelovecraft/next-vitals-guard --max-kb=220 --max-chunk-kb=90

Next.js Client Bundle Budget (.next/static/chunks)

CHUNK                                         RAW       GZIP
--------------------------------------------------------------
1q4itoff--b7w.js                          223.5 kB    69.9 kB
1bf99sczvmond.js                          160.5 kB    43.6 kB
0cz1d0mv5g_q7.js                            110 kB    38.7 kB
--------------------------------------------------------------
Total Client JS (gzip): 184.4 kB (budget: 220 kB)

✓ All client chunks within Core Web Vitals budget.

Because it relies only on Node's built-in fs, path, and zlib modules, it runs in under 200ms in GitHub Actions without headless Chrome overhead.

Takeaway

You don't have to guess whether a deploy hurt your search visibility or your bundle budget. Run geo-audit against your staging URL and next-vitals-guard after next build so every release stays fast and citable.

  • Technical SEO
  • GEO
  • Core Web Vitals
  • Next.js
  • JSON-LD

Keep reading