seo findable by search engines, AI engines and agents
SEO and GEO (AI search / answer-engine visibility) for websites. Covers server-rendered content, titles, descriptions and canonicals, Open Graph images…
Use when building or launching any public website, landing page, blog, docs or product page; when adding pages, metadata, generateMetadata, sitemap, robots, OG images or schema; when asked about SEO, AI SEO, GEO, AEO, llms.txt, rich results, or "why isn't my site showing in Google/ChatGPT"; or when `void seo` or `void geo` report findings.
Classic SEO is the foundation of GEO. Nearly all measurable AI-search visibility comes from being indexed, snippet-eligible and having complete raw HTML.
Canonical implementations live in templates/next/: src/site.config.ts (facts), src/content/pages.ts (page registry), src/lib/seo.ts (pageMetadata), src/lib/schema.ts + src/components/json-ld.tsx, src/app/{sitemap.ts,robots.ts,manifest.ts,opengraph-image.tsx}, src/app/llms.txt/route.ts, src/app/md/[...slug]/route.ts, next.config.ts (htmlLimitedBots, .md rewrites). Copy from there. The references hold the same code for other projects.
The raw HTML response is complete. Main content in reading order, full , JSON-LD and links, without running JavaScript. OpenAI, Anthropic, Perplexity and Meta crawlers don't execute JS.
Exactly one , and absolute in of the raw HTML on every indexable page. The canonical is self-referential and set per page, never in the root layout.
Real HTTP status codes. 200 for pages, 404 for unknown URLs (never a 200 "not found"), 301/308 for moves. In Next, call notFound()before any .
Keep metadata static (prerendered, or 'use cache'). If a route must be request-time, add Googlebot and the AI bots to htmlLimitedBots, or Next streams title and canonical into .
One metadata helper (pageMetadata()) for every page. Next merges metadata shallowly, so a page's openGraph silently drops the layout's siteName, locale and images.
One per page that matches the title's topic. Landmarks , , , ; for posts.
Structured data describes only what is visible and true. No invented ratings, reviews, prices or FAQs. One @graph with stable @ids. Escape < in JSON-LD.
robots.txt separates training bots from retrieval bots. Blocking GPTBot is a business choice. Blocking OAI-SearchBot, Claude-SearchBot or PerplexityBot removes you from AI answers. Previews send Disallow: /.
Sitemap lastModified is the real content date, never new Date() or the build time. Exclude noindex, auth and utility routes.
Write to be quoted. Answer first, then evidence: numbers with units and dates, links to primary sources, named quotes. Keep key facts visible, not hidden in tabs or images.
canonical: "/"; Organization + WebSite in the @graph (on this page or emitted from the root layout); opengraph-image; one h1 naming what the product is and who it's for; first paragraph states it in 1–2 sentences
Content page (about, features, pricing)
pageMetadata({ path, title, description }); BreadcrumbList if nested; one h1; descriptive h2s; comparison
where relevant
Article / blog post
pageMetadata({ type: "article", publishedTime, modifiedTime }); BlogPosting + BreadcrumbList; visible byline + "Updated" date matching JSON-LD; per-post opengraph-image; .md mirror; generateStaticParams + dynamicParams = false or notFound() before Suspense
Product / app / pricing
Product (buyable goods) or SoftwareApplication with the real, visible price. Add aggregateRatingonly when real ratings are shown on the page (without it SoftwareApplication is valid but not rich-result eligible). Prices in visible text
Docs page
TechArticle + BreadcrumbList; .md mirror advertised with alternates.types["text/markdown"]; listed in llms.txt; stable URLs; version in text
Author page
ProfilePage + Person with sameAs; linked from Article.author.url
App / auth / search results
robots: { index: false, follow: true }; not in sitemap, llms.txt or internal nav meant for crawlers; /login never canonicalizes to /
Title: unique, 10–60 chars, Topic | Brand. The brand name appears in every title; the home page uses an absolute tagline title (the helper does this for path: "/"). Don't put a plain string title in a nested layout.tsx: it resets the template for every child page.
Description: unique, 50–160 chars, a summary sentence rather than a keyword list. Fill it on every page.
Canonical: absolute (via metadataBase), no fragment, one URL form site-wide (https, one host, no trailing slash in Next's default). The same form in sitemap, internal links and hreflang. Never combine noindex with a cross-page canonical.
OG:og:title, og:type, og:image (1200×630 PNG/JPEG, absolute, ≤ 8 MB, not SVG), og:url = canonical, og:site_name, og:locale. Use the file convention opengraph-image.tsx per section or post. It also fills twitter:image.
Viewport: Next adds width=device-width, initial-scale=1. Never add maximum-scale=1 or user-scalable=no.
keywords meta is useless. Don't spend tokens on it.
Don't set nosnippet or a low max-snippet; they also remove you from AI Overviews. Put data-nosnippet only on cookie banners and boilerplate.
Uncompressed HTML < 1 MB (Googlebot stops at 2 MB, and the inlined RSC payload roughly doubles text weight).
Streaming metadata. A request-time generateMetadata (using cookies(), headers(), connection() or uncached fetches) streams /canonical into for every UA not in htmlLimitedBots. The default list has no AI bots and no Googlebot. Google only accepts canonical in . Suspense content arrives in
, out of order
curl -A GPTBot shows after
Prefer static metadata (generateStaticParams, 'use cache'). Otherwise extend htmlLimitedBotsand keep Next's default list in the regex (setting it replaces the default). Recipe: next-metadata.md §3
Still yields Google rich results or knowledge features: Organization, WebSite (site name), BreadcrumbList, Article/BlogPosting/NewsArticle, Product (snippet or merchant listing), SoftwareApplication (requires aggregateRating or review), ProfilePage, VideoObject, Event, QAPage/DiscussionForumPosting, Recipe, JobPosting, LocalBusiness.
Gone. Don't add for rich results: FAQPage (not shown since 2026-05-07), HowTo (2023), sitelinks SearchAction (2024-11), plus ClaimReview, course info, estimated salary, vehicle listing, special announcement, practice problem.
Never fabricate. Every name, headline, price, ratingValue, author.name must appear in the visible text. No self-serving reviews on your own Organization. "Only publish what exists."
JSON-LD is server-rendered in raw HTML via a native (not next/script), with < escaped as \u003c.
Organization + WebSite: once per page graph, on the home page at minimum (the template emits them from the root layout). Other nodes reference {"@id": "https://site/#organization"}.
Dates are ISO 8601 with a timezone. dateModified ≥ datePublished, matching the visible date.
Ship /llms.txt: # Brand, a > one-sentence summary, curated ## Docs / ## Blog link lists pointing to .md URLs, and ## Optional. It's under 100 KB and prerendered.
It mainly serves coding agents, not answer engines: 97% of llms.txt files got zero requests in a 137K-domain study, and Google says it doesn't use them. Never claim it improves ranking. A 5xx is an error (Lighthouse fails it); a missing file is fine.
Docs and posts: page.md mirror with X-Robots-Tag: noindex + Link: ; rel="canonical", advertised with . Not in the sitemap.
Evidence (KDD 2024 GEO study, NeurIPS 2025 C-SEO Bench, Microsoft and Google guidance): adding citations, statistics and quotations lifted AI visibility 30–40%, most for pages not ranked #1. Keyword stuffing and "authoritative tone" did nothing. AI engines favor third-party (earned) coverage.
Answer first. The first paragraph under the h1 and under each question-style h2 answers in 1–2 self-contained sentences (≤ 320 chars) that name the entity ("Acme is…", not "It is…").
Evidence. Numbers with units and dates ("cuts build time from 47 s to 4.5 s, measured Sept 2026"), links to primary sources, quotes from named people.
for comparisons and specs, and paragraphs under ~150 words.
Entity clarity. The same brand and product name in title, h1, og:site_name, JSON-LD name and the llms.txt H1. A clear "what it is / who it's for / pricing / license" block.
Freshness. A visible "Updated" date that matches dateModified and sitemap lastmod. Update facts, not just dates.
Visible. Key facts are not only in images, video, PDFs, canvas, carousels or closed tabs. Use or plain sections.
Non-commodity. First-hand data, benchmarks, original examples, clear opinions. Near-duplicate pages get clustered and only one is used.
Don't: generate keyword-variant pages, rewrite content "for AI", chunk into micro-pages, add FAQPage for rich results, or fake mentions. Patterns and examples: references/content-for-citation.md.
next build, then void seo --start "next start -p 3000" --port 3000 --format md: status codes (seo/http-status, seo/soft-404), head placement per UA (seo/metadata-in-body), canonical (seo/canonical-*), titles/descriptions, h1, links, OG image fetch, icons, robots.txt, sitemap. Include an unknown URL in --routes (e.g. /this-page-does-not-exist) to prove 404s.
Manual spot checks: curl -sA "Googlebot/2.1" URL | head -c 3000; Google Rich Results Test for new types; Search Console and Bing Webmaster Tools "AI Performance" after launch. Details: the audit skill.