diff --git a/docs/start/config.json b/docs/start/config.json index 2c65ff2027b..c5851d41e99 100644 --- a/docs/start/config.json +++ b/docs/start/config.json @@ -168,6 +168,14 @@ { "label": "Rendering Markdown", "to": "framework/react/guide/rendering-markdown" + }, + { + "label": "SEO", + "to": "framework/react/guide/seo" + }, + { + "label": "LLM Optimization (LLMO)", + "to": "framework/react/guide/llmo" } ] }, @@ -265,6 +273,14 @@ { "label": "Tailwind CSS Integration", "to": "framework/solid/guide/tailwind-integration" + }, + { + "label": "SEO", + "to": "framework/solid/guide/seo" + }, + { + "label": "LLM Optimization (LLMO)", + "to": "framework/solid/guide/llmo" } ] } diff --git a/docs/start/framework/react/guide/llmo.md b/docs/start/framework/react/guide/llmo.md new file mode 100644 index 00000000000..25007890fac --- /dev/null +++ b/docs/start/framework/react/guide/llmo.md @@ -0,0 +1,363 @@ +--- +id: llmo +title: LLM Optimization (LLMO) +--- + +> [!NOTE] +> Looking for traditional search engine optimization? See the [SEO guide](./seo). + +## What is LLMO? + +**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. + +## How LLMO Differs from SEO + +| 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. + +## What TanStack Start Provides + +TanStack Start's features that support LLMO: + +- **Server-Side Rendering** - Ensures AI crawlers see fully rendered content +- **Structured Data** - JSON-LD support for machine-readable content +- **Document Head Management** - Meta tags that AI systems can parse +- **Server Routes** - Create machine-readable endpoints (APIs, feeds) + +## Structured Data for AI + +Structured data using schema.org vocabulary helps AI systems understand your content's meaning and context. This is perhaps the most important LLMO technique. + +### Article Schema + +```tsx +// 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, +}) +``` + +### Product Schema + +For e-commerce, product schema helps AI assistants provide accurate product information: + +```tsx +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, +}) +``` + +### Organization and Website Schema + +Add organization schema to your root route for site-wide context: + +```tsx +// 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 + +FAQ schema is particularly effective for LLMO—AI systems often extract Q&A pairs: + +```tsx +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, +}) +``` + +## Machine-Readable Endpoints + +Create API endpoints that AI systems and developers can consume directly: + +```ts +// 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}`, + }, + })), + }) + }, + }, + }, +}) +``` + +## Content Best Practices + +Beyond technical implementation, content structure matters for LLMO: + +### Clear, Factual Statements + +AI systems extract factual claims. Make your key information explicit: + +```tsx +// Good: Clear, extractable facts +function ProductDetails({ product }) { + return ( +
+

{product.name}

+

+ {product.name} is a {product.category} made by {product.brand}. It costs + ${product.price} and is available in {product.colors.join(', ')}. +

+
+ ) +} +``` + +### Hierarchical Structure + +Use proper heading hierarchy—AI systems use this to understand content organization: + +```tsx +function DocumentationPage() { + return ( +
+

Getting Started with TanStack Start

+ +
+

Installation

+

Install TanStack Start using npm...

+ +

Prerequisites

+

You'll need Node.js 18 or later...

+
+ +
+

Configuration

+

Configure your app in vite.config.ts...

+
+
+ ) +} +``` + +### Authoritative Attribution + +Include author information and sources—AI systems consider authority signals: + +```tsx +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, +}) +``` + +## llms.txt + +Some sites are adopting a `llms.txt` file (similar to `robots.txt`) to provide guidance to AI systems: + +```ts +// 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', + }, + }) + }, + }, + }, +}) +``` + +## Monitoring AI Citations + +Unlike traditional SEO with established analytics, LLMO monitoring is still evolving. Consider: + +- **Test with AI assistants** - Ask ChatGPT, Claude, and Perplexity about your product/content +- **Monitor brand mentions** - Track how AI systems describe your offerings +- **Validate structured data** - Use [Google's Rich Results Test](https://search.google.com/test/rich-results) and [Schema.org Validator](https://validator.schema.org/) +- **Check AI search engines** - Monitor presence in Perplexity, Bing Chat, and Google AI Overviews diff --git a/docs/start/framework/react/guide/seo.md b/docs/start/framework/react/guide/seo.md new file mode 100644 index 00000000000..f05fc63755c --- /dev/null +++ b/docs/start/framework/react/guide/seo.md @@ -0,0 +1,326 @@ +--- +id: seo +title: SEO +--- + +> [!NOTE] +> Looking to optimize for AI assistants and LLMs? See the [LLM Optimization (LLMO) guide](./llmo). + +## What is SEO, really? + +SEO (Search Engine Optimization) is often misunderstood as simply "showing up on Google" or a checkbox that a library can magically provide. In reality, SEO is a broad discipline focused on delivering valuable content that people need and making it easy for them to find. + +**Technical SEO** is a subset of SEO that developers interact with most directly. It involves using tools and APIs that satisfy the technical requirements of search engines, crawlers, rankers, and even LLMs. When someone says a framework has "good SEO support," they typically mean it provides the tools to make this process straightforward. + +TanStack Start provides comprehensive technical SEO capabilities, but you still need to put in the work to use them effectively. + +## What TanStack Start Provides + +TanStack Start gives you the building blocks for technical SEO: + +- **Server-Side Rendering (SSR)** - Ensures crawlers receive fully rendered HTML +- **Static Prerendering** - Pre-generates pages for optimal performance and crawlability +- **Document Head Management** - Full control over meta tags, titles, and structured data +- **Performance** - Fast load times through code-splitting, streaming, and optimal bundling + +## Document Head Management + +The `head` property on routes is your primary tool for SEO. It allows you to set page titles, meta descriptions, Open Graph tags, and more. + +### Basic Meta Tags + +```tsx +// src/routes/index.tsx +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/')({ + head: () => ({ + meta: [ + { title: 'My App - Home' }, + { + name: 'description', + content: 'Welcome to My App, a platform for...', + }, + ], + }), + component: HomePage, +}) +``` + +### Dynamic Meta Tags + +Use loader data to generate dynamic meta tags for content pages: + +```tsx +// 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 }, + { name: 'description', content: loaderData.post.excerpt }, + ], + }), + component: PostPage, +}) +``` + +### Open Graph and Social Sharing + +Open Graph tags control how your pages appear when shared on social media: + +```tsx +export const Route = createFileRoute('/posts/$postId')({ + loader: async ({ params }) => { + const post = await fetchPost(params.postId) + return { post } + }, + head: ({ loaderData }) => ({ + meta: [ + { title: loaderData.post.title }, + { name: 'description', content: loaderData.post.excerpt }, + // Open Graph + { property: 'og:title', content: loaderData.post.title }, + { property: 'og:description', content: loaderData.post.excerpt }, + { property: 'og:image', content: loaderData.post.coverImage }, + { property: 'og:type', content: 'article' }, + // Twitter Card + { name: 'twitter:card', content: 'summary_large_image' }, + { name: 'twitter:title', content: loaderData.post.title }, + { name: 'twitter:description', content: loaderData.post.excerpt }, + { name: 'twitter:image', content: loaderData.post.coverImage }, + ], + }), + component: PostPage, +}) +``` + +### Canonical URLs + +Canonical URLs help prevent duplicate content issues: + +```tsx +export const Route = createFileRoute('/posts/$postId')({ + head: ({ params }) => ({ + links: [ + { + rel: 'canonical', + href: `https://myapp.com/posts/${params.postId}`, + }, + ], + }), + component: PostPage, +}) +``` + +## Structured Data (JSON-LD) + +Structured data helps search engines understand your content and can enable rich results in search: + +```tsx +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, + }, + datePublished: loaderData.post.publishedAt, + }), + }, + ], + }), + component: PostPage, +}) +``` + +## Server-Side Rendering + +SSR is enabled by default in TanStack Start. This ensures that search engine crawlers receive fully rendered HTML content, which is critical for SEO. + +```tsx +// SSR is automatic - your pages are rendered on the server +export const Route = createFileRoute('/about')({ + component: AboutPage, +}) +``` + +For routes that don't need SSR, you can disable it selectively. However, be aware this may impact SEO for those pages: + +```tsx +// Only disable SSR for pages that don't need SEO +export const Route = createFileRoute('/dashboard')({ + ssr: false, // Dashboard doesn't need to be indexed + component: DashboardPage, +}) +``` + +See the [Selective SSR guide](./selective-ssr) for more details. + +## Static Prerendering + +For content that doesn't change frequently, static prerendering generates HTML at build time for optimal performance: + +```ts +// vite.config.ts +import { tanstackStart } from '@tanstack/react-start/plugin/vite' + +export default defineConfig({ + plugins: [ + tanstackStart({ + prerender: { + enabled: true, + crawlLinks: true, + }, + }), + ], +}) +``` + +Prerendered pages load faster and are easily crawlable. See the [Static Prerendering guide](./static-prerendering) for configuration options. + +## Sitemaps + +### Built-in Sitemap Generation + +TanStack Start can automatically generate a sitemap when you enable prerendering with link crawling: + +```ts +// vite.config.ts +import { tanstackStart } from '@tanstack/react-start/plugin/vite' + +export default defineConfig({ + plugins: [ + tanstackStart({ + prerender: { + enabled: true, + crawlLinks: true, // Discovers all linkable pages + }, + sitemap: { + enabled: true, + host: 'https://myapp.com', + }, + }), + ], +}) +``` + +The sitemap is generated at build time by crawling all discoverable pages from your routes. This is the recommended approach for static or mostly-static sites. + +### Dynamic Sitemap + +For sites with dynamic content that can't be discovered at build time, you can create a dynamic sitemap using a [server route](./server-routes). Consider caching this at your CDN for performance: + +```ts +// src/routes/sitemap[.]xml.ts +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/sitemap.xml')({ + server: { + handlers: { + GET: async () => { + const posts = await fetchAllPosts() + + const sitemap = ` + + + https://myapp.com/ + daily + 1.0 + + ${posts + .map( + (post) => ` + + https://myapp.com/posts/${post.id} + ${post.updatedAt} + weekly + `, + ) + .join('')} +` + + return new Response(sitemap, { + headers: { + 'Content-Type': 'application/xml', + }, + }) + }, + }, + }, +}) +``` + +## robots.txt + +You can create a robots.txt file using a [server route](./server-routes): + +```ts +// src/routes/robots[.]txt.ts +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/robots.txt')({ + server: { + handlers: { + GET: async () => { + const robots = `User-agent: * +Allow: / + +Sitemap: https://myapp.com/sitemap.xml` + + return new Response(robots, { + headers: { + 'Content-Type': 'text/plain', + }, + }) + }, + }, + }, +}) +``` + +## Best Practices + +### Performance Matters + +Page speed is a ranking factor. TanStack Start helps with: + +- **Automatic code-splitting** - Only load the JavaScript needed for each page +- **Streaming SSR** - Start sending HTML to the browser immediately +- **Preloading** - Prefetch routes before users navigate to them + +### Content is King + +Technical SEO is just one piece of the puzzle. The most important factors are: + +- **Quality content** - Create content that provides value to users +- **Clear site structure** - Organize your routes logically +- **Descriptive URLs** - Use meaningful path segments (`/posts/my-great-article` vs `/posts/123`) +- **Internal linking** - Help users and crawlers discover your content + +### Test Your Implementation + +Use these tools to verify your SEO implementation: + +- [Google Search Console](https://search.google.com/search-console) - Monitor indexing and search performance +- [Google Rich Results Test](https://search.google.com/test/rich-results) - Validate structured data +- [Open Graph Debugger](https://developers.facebook.com/tools/debug/) - Preview social sharing cards +- Browser DevTools - Inspect rendered HTML and meta tags diff --git a/docs/start/framework/solid/guide/llmo.md b/docs/start/framework/solid/guide/llmo.md new file mode 100644 index 00000000000..770f32e8d7a --- /dev/null +++ b/docs/start/framework/solid/guide/llmo.md @@ -0,0 +1,364 @@ +--- +id: llmo +title: LLM Optimization (LLMO) +--- + +> [!NOTE] +> Looking for traditional search engine optimization? See the [SEO guide](./seo). + +## What is LLMO? + +**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. + +## How LLMO Differs from SEO + +| 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. + +## What TanStack Start Provides + +TanStack Start's features that support LLMO: + +- **Server-Side Rendering** - Ensures AI crawlers see fully rendered content +- **Structured Data** - JSON-LD support for machine-readable content +- **Document Head Management** - Meta tags that AI systems can parse +- **Server Routes** - Create machine-readable endpoints (APIs, feeds) + +## Structured Data for AI + +Structured data using schema.org vocabulary helps AI systems understand your content's meaning and context. This is perhaps the most important LLMO technique. + +### Article Schema + +```tsx +// src/routes/posts/$postId.tsx +import { createFileRoute } from '@tanstack/solid-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, +}) +``` + +### Product Schema + +For e-commerce, product schema helps AI assistants provide accurate product information: + +```tsx +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, +}) +``` + +### Organization and Website Schema + +Add organization schema to your root route for site-wide context: + +```tsx +// 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 + +FAQ schema is particularly effective for LLMO—AI systems often extract Q&A pairs: + +```tsx +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, +}) +``` + +## Machine-Readable Endpoints + +Create API endpoints that AI systems and developers can consume directly: + +```ts +// src/routes/api/products.ts +import { createFileRoute } from '@tanstack/solid-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}`, + }, + })), + }) + }, + }, + }, +}) +``` + +## Content Best Practices + +Beyond technical implementation, content structure matters for LLMO: + +### Clear, Factual Statements + +AI systems extract factual claims. Make your key information explicit: + +```tsx +// Good: Clear, extractable facts +function ProductDetails(props) { + return ( +
+

{props.product.name}

+

+ {props.product.name} is a {props.product.category} made by{' '} + {props.product.brand}. It costs ${props.product.price} and is available + in {props.product.colors.join(', ')}. +

+
+ ) +} +``` + +### Hierarchical Structure + +Use proper heading hierarchy—AI systems use this to understand content organization: + +```tsx +function DocumentationPage() { + return ( +
+

Getting Started with TanStack Start

+ +
+

Installation

+

Install TanStack Start using npm...

+ +

Prerequisites

+

You'll need Node.js 18 or later...

+
+ +
+

Configuration

+

Configure your app in vite.config.ts...

+
+
+ ) +} +``` + +### Authoritative Attribution + +Include author information and sources—AI systems consider authority signals: + +```tsx +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, +}) +``` + +## llms.txt + +Some sites are adopting a `llms.txt` file (similar to `robots.txt`) to provide guidance to AI systems: + +```ts +// src/routes/llms[.]txt.ts +import { createFileRoute } from '@tanstack/solid-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', + }, + }) + }, + }, + }, +}) +``` + +## Monitoring AI Citations + +Unlike traditional SEO with established analytics, LLMO monitoring is still evolving. Consider: + +- **Test with AI assistants** - Ask ChatGPT, Claude, and Perplexity about your product/content +- **Monitor brand mentions** - Track how AI systems describe your offerings +- **Validate structured data** - Use [Google's Rich Results Test](https://search.google.com/test/rich-results) and [Schema.org Validator](https://validator.schema.org/) +- **Check AI search engines** - Monitor presence in Perplexity, Bing Chat, and Google AI Overviews diff --git a/docs/start/framework/solid/guide/seo.md b/docs/start/framework/solid/guide/seo.md new file mode 100644 index 00000000000..0e9a49edbdd --- /dev/null +++ b/docs/start/framework/solid/guide/seo.md @@ -0,0 +1,326 @@ +--- +id: seo +title: SEO +--- + +> [!NOTE] +> Looking to optimize for AI assistants and LLMs? See the [LLM Optimization (LLMO) guide](./llmo). + +## What is SEO, really? + +SEO (Search Engine Optimization) is often misunderstood as simply "showing up on Google" or a checkbox that a library can magically provide. In reality, SEO is a broad discipline focused on delivering valuable content that people need and making it easy for them to find. + +**Technical SEO** is a subset of SEO that developers interact with most directly. It involves using tools and APIs that satisfy the technical requirements of search engines, crawlers, rankers, and even LLMs. When someone says a framework has "good SEO support," they typically mean it provides the tools to make this process straightforward. + +TanStack Start provides comprehensive technical SEO capabilities, but you still need to put in the work to use them effectively. + +## What TanStack Start Provides + +TanStack Start gives you the building blocks for technical SEO: + +- **Server-Side Rendering (SSR)** - Ensures crawlers receive fully rendered HTML +- **Static Prerendering** - Pre-generates pages for optimal performance and crawlability +- **Document Head Management** - Full control over meta tags, titles, and structured data +- **Performance** - Fast load times through code-splitting, streaming, and optimal bundling + +## Document Head Management + +The `head` property on routes is your primary tool for SEO. It allows you to set page titles, meta descriptions, Open Graph tags, and more. + +### Basic Meta Tags + +```tsx +// src/routes/index.tsx +import { createFileRoute } from '@tanstack/solid-router' + +export const Route = createFileRoute('/')({ + head: () => ({ + meta: [ + { title: 'My App - Home' }, + { + name: 'description', + content: 'Welcome to My App, a platform for...', + }, + ], + }), + component: HomePage, +}) +``` + +### Dynamic Meta Tags + +Use loader data to generate dynamic meta tags for content pages: + +```tsx +// src/routes/posts/$postId.tsx +import { createFileRoute } from '@tanstack/solid-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 }, + { name: 'description', content: loaderData.post.excerpt }, + ], + }), + component: PostPage, +}) +``` + +### Open Graph and Social Sharing + +Open Graph tags control how your pages appear when shared on social media: + +```tsx +export const Route = createFileRoute('/posts/$postId')({ + loader: async ({ params }) => { + const post = await fetchPost(params.postId) + return { post } + }, + head: ({ loaderData }) => ({ + meta: [ + { title: loaderData.post.title }, + { name: 'description', content: loaderData.post.excerpt }, + // Open Graph + { property: 'og:title', content: loaderData.post.title }, + { property: 'og:description', content: loaderData.post.excerpt }, + { property: 'og:image', content: loaderData.post.coverImage }, + { property: 'og:type', content: 'article' }, + // Twitter Card + { name: 'twitter:card', content: 'summary_large_image' }, + { name: 'twitter:title', content: loaderData.post.title }, + { name: 'twitter:description', content: loaderData.post.excerpt }, + { name: 'twitter:image', content: loaderData.post.coverImage }, + ], + }), + component: PostPage, +}) +``` + +### Canonical URLs + +Canonical URLs help prevent duplicate content issues: + +```tsx +export const Route = createFileRoute('/posts/$postId')({ + head: ({ params }) => ({ + links: [ + { + rel: 'canonical', + href: `https://myapp.com/posts/${params.postId}`, + }, + ], + }), + component: PostPage, +}) +``` + +## Structured Data (JSON-LD) + +Structured data helps search engines understand your content and can enable rich results in search: + +```tsx +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, + }, + datePublished: loaderData.post.publishedAt, + }), + }, + ], + }), + component: PostPage, +}) +``` + +## Server-Side Rendering + +SSR is enabled by default in TanStack Start. This ensures that search engine crawlers receive fully rendered HTML content, which is critical for SEO. + +```tsx +// SSR is automatic - your pages are rendered on the server +export const Route = createFileRoute('/about')({ + component: AboutPage, +}) +``` + +For routes that don't need SSR, you can disable it selectively. However, be aware this may impact SEO for those pages: + +```tsx +// Only disable SSR for pages that don't need SEO +export const Route = createFileRoute('/dashboard')({ + ssr: false, // Dashboard doesn't need to be indexed + component: DashboardPage, +}) +``` + +See the [Selective SSR guide](./selective-ssr) for more details. + +## Static Prerendering + +For content that doesn't change frequently, static prerendering generates HTML at build time for optimal performance: + +```ts +// vite.config.ts +import { tanstackStart } from '@tanstack/solid-start/plugin/vite' + +export default defineConfig({ + plugins: [ + tanstackStart({ + prerender: { + enabled: true, + crawlLinks: true, + }, + }), + ], +}) +``` + +Prerendered pages load faster and are easily crawlable. See the [Static Prerendering guide](./static-prerendering) for configuration options. + +## Sitemaps + +### Built-in Sitemap Generation + +TanStack Start can automatically generate a sitemap when you enable prerendering with link crawling: + +```ts +// vite.config.ts +import { tanstackStart } from '@tanstack/solid-start/plugin/vite' + +export default defineConfig({ + plugins: [ + tanstackStart({ + prerender: { + enabled: true, + crawlLinks: true, // Discovers all linkable pages + }, + sitemap: { + enabled: true, + host: 'https://myapp.com', + }, + }), + ], +}) +``` + +The sitemap is generated at build time by crawling all discoverable pages from your routes. This is the recommended approach for static or mostly-static sites. + +### Dynamic Sitemap + +For sites with dynamic content that can't be discovered at build time, you can create a dynamic sitemap using a [server route](./server-routes). Consider caching this at your CDN for performance: + +```ts +// src/routes/sitemap[.]xml.ts +import { createFileRoute } from '@tanstack/solid-router' + +export const Route = createFileRoute('/sitemap.xml')({ + server: { + handlers: { + GET: async () => { + const posts = await fetchAllPosts() + + const sitemap = ` + + + https://myapp.com/ + daily + 1.0 + + ${posts + .map( + (post) => ` + + https://myapp.com/posts/${post.id} + ${post.updatedAt} + weekly + `, + ) + .join('')} +` + + return new Response(sitemap, { + headers: { + 'Content-Type': 'application/xml', + }, + }) + }, + }, + }, +}) +``` + +## robots.txt + +You can create a robots.txt file using a [server route](./server-routes): + +```ts +// src/routes/robots[.]txt.ts +import { createFileRoute } from '@tanstack/solid-router' + +export const Route = createFileRoute('/robots.txt')({ + server: { + handlers: { + GET: async () => { + const robots = `User-agent: * +Allow: / + +Sitemap: https://myapp.com/sitemap.xml` + + return new Response(robots, { + headers: { + 'Content-Type': 'text/plain', + }, + }) + }, + }, + }, +}) +``` + +## Best Practices + +### Performance Matters + +Page speed is a ranking factor. TanStack Start helps with: + +- **Automatic code-splitting** - Only load the JavaScript needed for each page +- **Streaming SSR** - Start sending HTML to the browser immediately +- **Preloading** - Prefetch routes before users navigate to them + +### Content is King + +Technical SEO is just one piece of the puzzle. The most important factors are: + +- **Quality content** - Create content that provides value to users +- **Clear site structure** - Organize your routes logically +- **Descriptive URLs** - Use meaningful path segments (`/posts/my-great-article` vs `/posts/123`) +- **Internal linking** - Help users and crawlers discover your content + +### Test Your Implementation + +Use these tools to verify your SEO implementation: + +- [Google Search Console](https://search.google.com/search-console) - Monitor indexing and search performance +- [Google Rich Results Test](https://search.google.com/test/rich-results) - Validate structured data +- [Open Graph Debugger](https://developers.facebook.com/tools/debug/) - Preview social sharing cards +- Browser DevTools - Inspect rendered HTML and meta tags