Sacred Lantern Studio
2026-11-09· 10 minSEONext.jsChecklistLaunch

Next.js technical SEO checklist: 22 checks you can actually run

Crawlability, content structure, structured data, performance and internationalisation — 22 checks with the exact curl commands, pass criteria and fix direction. All from real projects.

This checklist comes from real work: a 126-page site rebuild, then a bilingual restructure. Every item earned its place by breaking something.

Work through it in order. Don't skip the first eight — if the basics are wrong, everything after them is decoration.


1. Crawlability (8 checks)

1. Does the initial HTML contain body text?

The most common failure.

curl -s https://your-site.com/page | python3 -c "
import sys, re, html
raw = sys.stdin.read()
m = re.search(r'<main[^>]*>(.*?)</main>', raw, flags=re.S|re.I)
seg = m.group(1) if m else raw
t = re.sub(r'<(script|style)[^>]*>.*?</\1>', ' ', seg, flags=re.S|re.I)
t = re.sub(r'<[^>]+>', ' ', t)
print('body length:', len(re.sub(r'\s+', ' ', html.unescape(t)).strip()))
"

Pass: over 500 characters. I've seen 46 — the entire content was Loading….

Cause: client-side rendering. Crawlers don't execute JS on the first pass.

2. Is robots.txt blocking anything by accident?

curl -s https://your-site.com/robots.txt

Pass: no Disallow: /, sitemap URL correct.

Trap: a middleware allowlist that misses .txt — which silently tells AI crawlers the site is gone.

3. Is the sitemap complete and reachable?

curl -s -o /dev/null -w "%{http_code} %{size_download}\n" https://your-site.com/sitemap.xml
curl -s https://your-site.com/sitemap.xml | grep -c "<loc>"

Pass: 200, count matches actual pages.

Trap: lastmod set to new Date(). Every deploy marks all URLs as updated today; Google eventually distrusts the field and ignores it.

4. Are status codes correct?

for u in / /about /nonexistent-page; do
  printf "%-20s " "$u"
  curl -s -o /dev/null -w "%{http_code}\n" "https://your-site.com$u"
done

Pass: 200 for real pages, 404 for missing ones.

Trap: returning 410 for every unknown path. 410 means "permanently deleted" — a typo shouldn't tell search engines that, and you lose error monitoring signal.

5. Is canonical self-referencing?

curl -s https://your-site.com/page | grep -o '<link rel="canonical"[^>]*>'

Pass: points at itself, at the final URL (not a redirect source).

Trap: canonical says /services/en while that URL 308s to /en.

6. Any accidental noindex?

curl -s https://your-site.com/page | grep -o '<meta name="robots"[^>]*>'
curl -sI https://your-site.com/page | grep -i x-robots-tag

Pass: index, follow on content pages.

7. Single-hop redirects only

curl -sIL https://your-site.com/old-page | grep -E "^HTTP|^location"

Pass: no A → B → C chains.

8. Are static assets blocked?

curl -s -o /dev/null -w "%{http_code}\n" https://your-site.com/_next/static/chunks/main.js

Pass: 200. An over-broad middleware matcher that catches /_next/static produces a blank page.


2. Content structure (4 checks)

9. Exactly one H1

curl -s https://your-site.com/page | grep -c "<h1"

Pass: 1.

In a sample of 13 US small-business sites I audited, only 7 had exactly one — three had none at all.

10. Are H2s used for sections?

Pass: long pages broken into H2 sections.

Trap: <div class="text-2xl font-bold"> pretending to be a heading. Looks right, reads as nothing.

11. Unique titles

curl -s https://your-site.com/sitemap.xml | grep -o '<loc>[^<]*' | sed 's|<loc>||' | while read u; do
  echo "$(curl -s "$u" | grep -o '<title>[^<]*' | head -1) | $u"
done

Pass: no duplicates.

12. Meta description length

Pass: 60–155 characters. Enforce it in code with description.slice(0, 155).


3. Structured data (4 checks)

13. Is JSON-LD present?

curl -s https://your-site.com/page | grep -o '"@type":"[A-Za-z]*"' | sort | uniq -c
Page type Expected types
Homepage Organization, WebSite
Service page Service, FAQPage
Article TechArticle, BreadcrumbList
Product Product, Offer

14. Are FAQs marked up?

A visible FAQ block without FAQPage schema is invisible to extraction. Add mainEntity with Question/Answer pairs.

15. Does Service reference provider by @id?

{ "@type": "Service", "provider": { "@id": "https://your-site.com/#organization" } }

Reference, don't redeclare — one entity, many mentions.

16. Are prices in the schema?

"offers": [{ "@type": "Offer", "name": "Starter", "price": "1200", "priceCurrency": "USD" }]

Pages with structured pricing get cited more readily when AI answers "how much does this cost".


4. Performance (4 checks)

17. Core Web Vitals

npx lighthouse https://your-site.com --output=json --output-path=lh.json --chrome-flags="--headless=new"
Metric Target
LCP < 2.5s
CLS < 0.1
TBT < 200ms

18. Modern image formats

UI screenshots should be WebP. Measured: four PNGs at 955 KB became 105 KB as WebP — 11%, and 6% on flat-colour screenshots.

19. Blocking scripts

A <head> script that rewrote a lang attribute pushed TBT from 60ms to 240ms. Removing it (static markup instead) brought it back to 60ms.

20. Fonts

An 8–10 MB CJK webfont destroys LCP. Use a system font stack or load a subset.


5. Internationalisation (2 checks)

21. Does html lang match the page language?

for u in / /en; do
  printf "%-6s " "$u"
  curl -s "https://your-site.com$u" | grep -o 'lang="[a-zA-Z-]*"' | head -1
done

Pass: / is zh-CN, /en is en.

This is the hardest one. In App Router, <html lang> can only be set once by the root layout. Per-route switching requires route groups:

app/(zh)/layout.tsx   <html lang="zh-CN">
app/(en)/layout.tsx   <html lang="en">

Patching it client-side doesn't work — crawlers see the wrong value.

22. Is hreflang bidirectional with x-default?

curl -s https://your-site.com/en | grep -o 'hrefLang="[^"]*"'

Pass: every language version lists all languages including itself, plus x-default.

Trap: page-level metadata replaces the layout's entire alternates object rather than merging. That's how /en lost its hreflang after a restructure.


Five-minute version

If you only have five minutes:

  1. Body length on the homepage (check 1)
  2. /robots.txt (check 2)
  3. sitemap.xml count (check 3)
  4. html lang (check 21)

If any of those fail, the rest doesn't matter.


Automate the basics

Checks 1, 2, 3, 9, 13 and 18 can be automated: free site audit tool — domain in, results in 30 seconds, no signup.

If it surfaces more than you want to fix yourself, the technical SEO service covers the full process and pricing.

Does your site have these problems?

Run the free audit tool for a 30-second report, or just write to me and describe the situation — I will tell you whether it is worth fixing.