SEO
Sitemap generation and SEO implementation strategy.
Overview
BetterStarter includes built-in SEO features:
- Dynamic sitemap.xml - Auto-generated from your routes
- Meta tags - Configured per page via route metadata
- Open Graph - Social media sharing
- Structured data - Schema.org JSON-LD
Sitemap Generation
Sitemap is automatically generated at /sitemap.xml and submitted to search engines.
Generated Sitemap
Located at src/routes/sitemap[.]xml.ts:
export const Route = createFileRoute('/sitemap[.]xml')({
loader: async () => {
const urls = [
{ url: '/', lastmod: new Date() },
{ url: '/blog/', lastmod: new Date() },
// Blog posts are auto-included
// Docs pages are auto-included
]
return urls
},
component: SitemapComponent,
})Submit to Search Engines
Add to robots.txt:
User-agent: *
Allow: /
Sitemap: https://yourdomain.com/sitemap.xmlThen submit to:
Meta Tags
Use generatePageSEO() helper in route loaders:
import { generatePageSEO } from '@/lib/seo'
export const Route = createFileRoute('/docs')({
head: () => ({
meta: generatePageSEO({
title: 'Documentation',
description: 'Learn how to build with BetterStarter',
image: 'https://betterstarter.dev/og-image.jpg',
url: 'https://betterstarter.dev/docs',
}),
}),
component: DocsView,
})Meta Tags Generated
<meta name="description">- Page description<meta name="viewport">- Responsive<meta property="og:title">- Social sharing title<meta property="og:description">- Social sharing description<meta property="og:image">- Social sharing image<meta property="og:url">- Canonical URL<meta name="twitter:card">- Twitter card type
Open Graph Images
Social platforms use Open Graph images for link previews. Provide relevant images:
generatePageSEO({
title: 'My Blog Post',
description: 'An interesting post',
image: 'https://betterstarter.dev/blog-assets/post-image.jpg',
})Image recommendations:
- Size: 1200x630 pixels (16:9 ratio)
- Format: JPG or PNG
- File size: < 5MB for fast loading
Structured Data (Schema.org)
Add JSON-LD for rich snippets in search results:
// In a blog post route
const schema = {
'@context': 'https://schema.org',
'@type': 'BlogPosting',
headline: 'My awesome post',
description: 'A fascinating read',
datePublished: '2026-02-28',
author: {
'@type': 'Person',
name: 'Developer Name',
},
}
// Include in head
export const Route = createFileRoute('/blog/$slug')({
head: () => ({
meta: generatePageSEO({
title: postTitle,
description: postDescription,
}),
scripts: [
{
type: 'application/ld+json',
children: JSON.stringify(schema),
},
],
}),
})Canonical URLs
Always set canonical URLs to prevent duplicate content issues:
generatePageSEO({
url: 'https://betterstarter.dev/blog/my-post',
})This prevents issues if the same content is accessible via multiple URLs.
Best Practices
Titles
- Length: 50-60 characters
- Format: "Page Title | BetterStarter"
- Keywords: Include main keyword naturally
Descriptions
- Length: 150-160 characters
- Call to action: "Learn how to..." or "Discover..."
- Keywords: Include 1-2 relevant keywords
URLs
- Structure:
/category/topicor/category/year/month/slug - Hyphens: Use hyphens for word boundaries
- Lowercase: Always lowercase
- Descriptive: Avoid IDs or random characters
Content
- Use semantic HTML (H1, H2, H3)
- Include alt text on images
- Link to related content internally
- Keep content updated and fresh
RSS Feed
Blog RSS feed at /rss.xml for feed readers:
export const Route = createFileRoute('/rss[.]xml')({
component: RssFeedComponent,
})Automatically includes all published blog posts with:
- Title, description, publication date
- Author information
- Full content or excerpt
Robots.txt
Control search engine crawling with public/robots.txt:
User-agent: *
Allow: /
Disallow: /api/
Disallow: /admin/
Sitemap: https://yourdomain.com/sitemap.xmlMonitoring
Use Google Search Console to monitor:
- Search impressions and clicks
- Indexed pages
- Crawl errors
- Mobile usability