Looking for traditional search engine optimization? See the SEO guide.
LLM Optimization (LLMO), also known as AI Optimization (AIO) or Generative Engine Optimization (GEO), is the practice of structuring your content and data so that AI systems—like ChatGPT, Claude, Perplexity, and other LLM-powered tools—can accurately understand, cite, and recommend your content.
While traditional SEO focuses on ranking in search engine results pages, LLMO focuses on being accurately represented in AI-generated responses. As more users get information through AI assistants rather than traditional search, this is becoming increasingly important.
| Aspect | SEO | LLMO |
|---|---|---|
| Goal | Rank in search results | Be cited/recommended by AI |
| Audience | Search engine crawlers | LLM training & retrieval systems |
| Key signals | Links, keywords, page speed | Structured data, clarity, authority |
| Content format | Optimized for snippets | Optimized for extraction & synthesis |
The good news: many LLMO best practices overlap with SEO. Clear structure, authoritative content, and good metadata help both.
TanStack Start's features that support LLMO:
Structured data using schema.org vocabulary helps AI systems understand your content's meaning and context. This is perhaps the most important LLMO technique.
// src/routes/posts/$postId.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params }) => {
const post = await fetchPost(params.postId)
return { post }
},
head: ({ loaderData }) => ({
meta: [{ title: loaderData.post.title }],
scripts: [
{
type: 'application/ld+json',
children: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'Article',
headline: loaderData.post.title,
description: loaderData.post.excerpt,
image: loaderData.post.coverImage,
author: {
'@type': 'Person',
name: loaderData.post.author.name,
url: loaderData.post.author.url,
},
publisher: {
'@type': 'Organization',
name: 'My Company',
logo: {
'@type': 'ImageObject',
url: 'https://myapp.com/logo.png',
},
},
datePublished: loaderData.post.publishedAt,
dateModified: loaderData.post.updatedAt,
}),
},
],
}),
component: PostPage,
})
// src/routes/posts/$postId.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params }) => {
const post = await fetchPost(params.postId)
return { post }
},
head: ({ loaderData }) => ({
meta: [{ title: loaderData.post.title }],
scripts: [
{
type: 'application/ld+json',
children: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'Article',
headline: loaderData.post.title,
description: loaderData.post.excerpt,
image: loaderData.post.coverImage,
author: {
'@type': 'Person',
name: loaderData.post.author.name,
url: loaderData.post.author.url,
},
publisher: {
'@type': 'Organization',
name: 'My Company',
logo: {
'@type': 'ImageObject',
url: 'https://myapp.com/logo.png',
},
},
datePublished: loaderData.post.publishedAt,
dateModified: loaderData.post.updatedAt,
}),
},
],
}),
component: PostPage,
})
For e-commerce, product schema helps AI assistants provide accurate product information:
export const Route = createFileRoute('/products/$productId')({
loader: async ({ params }) => {
const product = await fetchProduct(params.productId)
return { product }
},
head: ({ loaderData }) => ({
meta: [{ title: loaderData.product.name }],
scripts: [
{
type: 'application/ld+json',
children: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'Product',
name: loaderData.product.name,
description: loaderData.product.description,
image: loaderData.product.images,
brand: {
'@type': 'Brand',
name: loaderData.product.brand,
},
offers: {
'@type': 'Offer',
price: loaderData.product.price,
priceCurrency: 'USD',
availability: loaderData.product.inStock
? 'https://schema.org/InStock'
: 'https://schema.org/OutOfStock',
},
aggregateRating: loaderData.product.rating
? {
'@type': 'AggregateRating',
ratingValue: loaderData.product.rating,
reviewCount: loaderData.product.reviewCount,
}
: undefined,
}),
},
],
}),
component: ProductPage,
})
export const Route = createFileRoute('/products/$productId')({
loader: async ({ params }) => {
const product = await fetchProduct(params.productId)
return { product }
},
head: ({ loaderData }) => ({
meta: [{ title: loaderData.product.name }],
scripts: [
{
type: 'application/ld+json',
children: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'Product',
name: loaderData.product.name,
description: loaderData.product.description,
image: loaderData.product.images,
brand: {
'@type': 'Brand',
name: loaderData.product.brand,
},
offers: {
'@type': 'Offer',
price: loaderData.product.price,
priceCurrency: 'USD',
availability: loaderData.product.inStock
? 'https://schema.org/InStock'
: 'https://schema.org/OutOfStock',
},
aggregateRating: loaderData.product.rating
? {
'@type': 'AggregateRating',
ratingValue: loaderData.product.rating,
reviewCount: loaderData.product.reviewCount,
}
: undefined,
}),
},
],
}),
component: ProductPage,
})
Add organization schema to your root route for site-wide context:
// src/routes/__root.tsx
export const Route = createRootRoute({
head: () => ({
meta: [
{ charSet: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
],
scripts: [
{
type: 'application/ld+json',
children: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'WebSite',
name: 'My App',
url: 'https://myapp.com',
publisher: {
'@type': 'Organization',
name: 'My Company',
url: 'https://myapp.com',
logo: 'https://myapp.com/logo.png',
sameAs: [
'https://twitter.com/mycompany',
'https://github.com/mycompany',
],
},
}),
},
],
}),
component: RootComponent,
})
// src/routes/__root.tsx
export const Route = createRootRoute({
head: () => ({
meta: [
{ charSet: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
],
scripts: [
{
type: 'application/ld+json',
children: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'WebSite',
name: 'My App',
url: 'https://myapp.com',
publisher: {
'@type': 'Organization',
name: 'My Company',
url: 'https://myapp.com',
logo: 'https://myapp.com/logo.png',
sameAs: [
'https://twitter.com/mycompany',
'https://github.com/mycompany',
],
},
}),
},
],
}),
component: RootComponent,
})
FAQ schema is particularly effective for LLMO—AI systems often extract Q&A pairs:
export const Route = createFileRoute('/faq')({
loader: async () => {
const faqs = await fetchFAQs()
return { faqs }
},
head: ({ loaderData }) => ({
meta: [{ title: 'Frequently Asked Questions' }],
scripts: [
{
type: 'application/ld+json',
children: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'FAQPage',
mainEntity: loaderData.faqs.map((faq) => ({
'@type': 'Question',
name: faq.question,
acceptedAnswer: {
'@type': 'Answer',
text: faq.answer,
},
})),
}),
},
],
}),
component: FAQPage,
})
export const Route = createFileRoute('/faq')({
loader: async () => {
const faqs = await fetchFAQs()
return { faqs }
},
head: ({ loaderData }) => ({
meta: [{ title: 'Frequently Asked Questions' }],
scripts: [
{
type: 'application/ld+json',
children: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'FAQPage',
mainEntity: loaderData.faqs.map((faq) => ({
'@type': 'Question',
name: faq.question,
acceptedAnswer: {
'@type': 'Answer',
text: faq.answer,
},
})),
}),
},
],
}),
component: FAQPage,
})
Create API endpoints that AI systems and developers can consume directly:
// src/routes/api/products.ts
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/api/products')({
server: {
handlers: {
GET: async ({ request }) => {
const url = new URL(request.url)
const category = url.searchParams.get('category')
const products = await fetchProducts({ category })
return Response.json({
'@context': 'https://schema.org',
'@type': 'ItemList',
itemListElement: products.map((product, index) => ({
'@type': 'ListItem',
position: index + 1,
item: {
'@type': 'Product',
name: product.name,
description: product.description,
url: `https://myapp.com/products/${product.id}`,
},
})),
})
},
},
},
})
// src/routes/api/products.ts
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/api/products')({
server: {
handlers: {
GET: async ({ request }) => {
const url = new URL(request.url)
const category = url.searchParams.get('category')
const products = await fetchProducts({ category })
return Response.json({
'@context': 'https://schema.org',
'@type': 'ItemList',
itemListElement: products.map((product, index) => ({
'@type': 'ListItem',
position: index + 1,
item: {
'@type': 'Product',
name: product.name,
description: product.description,
url: `https://myapp.com/products/${product.id}`,
},
})),
})
},
},
},
})
Beyond technical implementation, content structure matters for LLMO:
AI systems extract factual claims. Make your key information explicit:
// Good: Clear, extractable facts
function ProductDetails({ product }) {
return (
<article>
<h1>{product.name}</h1>
<p>
{product.name} is a {product.category} made by {product.brand}. It costs
${product.price} and is available in {product.colors.join(', ')}.
</p>
</article>
)
}
// Good: Clear, extractable facts
function ProductDetails({ product }) {
return (
<article>
<h1>{product.name}</h1>
<p>
{product.name} is a {product.category} made by {product.brand}. It costs
${product.price} and is available in {product.colors.join(', ')}.
</p>
</article>
)
}
Use proper heading hierarchy—AI systems use this to understand content organization:
function DocumentationPage() {
return (
<article>
<h1>Getting Started with TanStack Start</h1>
<section>
<h2>Installation</h2>
<p>Install TanStack Start using npm...</p>
<h3>Prerequisites</h3>
<p>You'll need Node.js 18 or later...</p>
</section>
<section>
<h2>Configuration</h2>
<p>Configure your app in vite.config.ts...</p>
</section>
</article>
)
}
function DocumentationPage() {
return (
<article>
<h1>Getting Started with TanStack Start</h1>
<section>
<h2>Installation</h2>
<p>Install TanStack Start using npm...</p>
<h3>Prerequisites</h3>
<p>You'll need Node.js 18 or later...</p>
</section>
<section>
<h2>Configuration</h2>
<p>Configure your app in vite.config.ts...</p>
</section>
</article>
)
}
Include author information and sources—AI systems consider authority signals:
export const Route = createFileRoute('/posts/$postId')({
head: ({ loaderData }) => ({
meta: [
{ title: loaderData.post.title },
{ name: 'author', content: loaderData.post.author.name },
{
property: 'article:author',
content: loaderData.post.author.profileUrl,
},
{
property: 'article:published_time',
content: loaderData.post.publishedAt,
},
],
}),
component: PostPage,
})
export const Route = createFileRoute('/posts/$postId')({
head: ({ loaderData }) => ({
meta: [
{ title: loaderData.post.title },
{ name: 'author', content: loaderData.post.author.name },
{
property: 'article:author',
content: loaderData.post.author.profileUrl,
},
{
property: 'article:published_time',
content: loaderData.post.publishedAt,
},
],
}),
component: PostPage,
})
Some sites are adopting a llms.txt file (similar to robots.txt) to provide guidance to AI systems:
// src/routes/llms[.]txt.ts
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/llms.txt')({
server: {
handlers: {
GET: async () => {
const content = `# My App
> My App is a platform for building modern web applications.
## Documentation
- Getting Started: https://myapp.com/docs/getting-started
- API Reference: https://myapp.com/docs/api
## Key Facts
- Built with TanStack Start
- Supports React and Solid
- Full TypeScript support
## Contact
- Website: https://myapp.com
- GitHub: https://github.com/mycompany/myapp
`
return new Response(content, {
headers: {
'Content-Type': 'text/plain',
},
})
},
},
},
})
// src/routes/llms[.]txt.ts
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/llms.txt')({
server: {
handlers: {
GET: async () => {
const content = `# My App
> My App is a platform for building modern web applications.
## Documentation
- Getting Started: https://myapp.com/docs/getting-started
- API Reference: https://myapp.com/docs/api
## Key Facts
- Built with TanStack Start
- Supports React and Solid
- Full TypeScript support
## Contact
- Website: https://myapp.com
- GitHub: https://github.com/mycompany/myapp
`
return new Response(content, {
headers: {
'Content-Type': 'text/plain',
},
})
},
},
},
})
Unlike traditional SEO with established analytics, LLMO monitoring is still evolving. Consider:
