SEO Configuration for the Tanstack Start Supabase Starter Kit
Configure sitemaps, metadata, structured data, and search engine optimization for your Makerkit SaaS application.
SEO in Makerkit starts with the TanStack Router route head option for page-level metadata, an auto-generated sitemap at /sitemap.xml, and a /robots.txt server route. The kit handles technical SEO out of the box, so you can focus on content quality and backlink strategy.
SEO Configuration
Set up search engine optimization for your SaaS
Page Metadata
Route head option
Set page-level SEO with the route head option. It returns meta and links arrays that TanStack Router merges into the document <head>:
apps/web/src/routes/_marketing/pricing.tsx
import { createFileRoute } from '@tanstack/react-router';export const Route = createFileRoute('/_marketing/pricing')({ head: () => ({ meta: [ { title: 'Pricing | Your SaaS Name' }, { name: 'description', content: 'Simple, transparent pricing. Start free, upgrade when you need more.', }, { property: 'og:title', content: 'Pricing | Your SaaS Name' }, { property: 'og:description', content: 'Simple, transparent pricing for teams of all sizes.', }, { property: 'og:image', content: '/images/og/pricing.png' }, { property: 'og:type', content: 'website' }, { name: 'twitter:card', content: 'summary_large_image' }, { name: 'twitter:title', content: 'Pricing | Your SaaS Name' }, { name: 'twitter:description', content: 'Simple, transparent pricing for teams of all sizes.', }, { name: 'twitter:image', content: '/images/og/pricing.png' }, ], }), component: PricingPage,});function PricingPage() { // ...}Dynamic Metadata
For pages with dynamic content, fetch the data in the route loader and derive head from the loaded data:
apps/web/src/routes/_marketing/blog/$slug.tsx
import { createFileRoute } from '@tanstack/react-router';import { createCmsClient } from '@kit/cms';export const Route = createFileRoute('/_marketing/blog/$slug')({ loader: async ({ params }) => { const cms = await createCmsClient(); const post = await cms.getContentBySlug({ slug: params.slug, collection: 'posts', }); return { post }; }, head: ({ loaderData }) => { const post = loaderData?.post; return { meta: [ { title: `${post?.title} | Your SaaS Blog` }, { name: 'description', content: post?.description }, { property: 'og:title', content: post?.title }, { property: 'og:description', content: post?.description }, { property: 'og:image', content: post?.image }, { property: 'og:type', content: 'article' }, ], }; }, component: BlogPostPage,});Global Metadata
Default document metadata lives in apps/web/src/lib/root-metadata.ts. The getRootHead helper builds the base meta + links, and the root route at apps/web/src/routes/__root.tsx returns them from its head(). Individual routes override these per page:
apps/web/src/lib/root-metadata.ts
import appConfig from '#/config/app.config.ts';export function getRootHead() { const title = appConfig.title; const description = appConfig.description; return { meta: [ { charSet: 'utf-8' }, { name: 'viewport', content: 'width=device-width, initial-scale=1' }, { title }, { name: 'description', content: description }, { name: 'application-name', content: appConfig.name }, { property: 'og:title', content: title }, { property: 'og:description', content: description }, { property: 'og:url', content: appConfig.url }, { property: 'og:site_name', content: appConfig.name }, { name: 'twitter:card', content: 'summary_large_image' }, { name: 'twitter:title', content: title }, { name: 'twitter:description', content: description }, ], links: [{ rel: 'icon', href: '/images/favicon/favicon.ico' }], };}Sitemap Configuration
Makerkit auto-generates a sitemap at /sitemap.xml. The configuration lives in the TanStack Start server route at apps/web/src/routes/sitemap[.]xml.ts (the bracketed filename escapes the dot so the route matches /sitemap.xml).
Adding Static Pages
Add new pages to the STATIC_PATHS array:
apps/web/src/routes/sitemap[.]xml.ts
// Static, publicly-indexable marketing paths. CMS-backed blog posts and docs// pages are appended dynamically below.const STATIC_PATHS = [ '/', '/pricing', '/faq', '/blog', '/docs', '/contact', '/about', // Add new pages '/features', '/privacy-policy', '/terms-of-service', '/cookie-policy',];The route builds the XML from these paths (prefixed with appConfig.url) and returns it from the GET server handler.
Dynamic Content
Blog posts and documentation pages are appended automatically. The CMS integration handles this inside the sitemap builder:
// Blog posts and docs are added automaticallyconst [posts, docs] = await Promise.all([ client.getContentItems({ collection: 'posts', content: false }), client.getContentItems({ collection: 'documentation', content: false }),]);posts.items.forEach((item) => paths.push(`/blog/${item.slug}`));docs.items.forEach((item) => paths.push(`/docs/${item.slug}`));Excluding Pages
Exclude pages from the sitemap by not including them in STATIC_PATHS. For pages that should not be indexed at all, add a robots meta tag from the route head:
head: () => ({ meta: [{ name: 'robots', content: 'noindex, nofollow' }],}),Structured Data
Add JSON-LD structured data for rich search results. Render a <script type="application/ld+json"> tag inside your route component.
Organization Schema
Add to your home page component:
apps/web/src/routes/_marketing/index.tsx
// JSON-LD structured data using a script tagfunction Home() { return ( <> <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify({ '@context': 'https://schema.org', '@type': 'Organization', name: 'Your SaaS Name', url: 'https://yoursaas.com', logo: 'https://yoursaas.com/logo.png', sameAs: [ 'https://twitter.com/yoursaas', 'https://github.com/yoursaas', ], }), }} /> {/* Page content */} </> );}Product Schema
Add to your pricing page:
apps/web/src/routes/_marketing/pricing.tsx
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify({ '@context': 'https://schema.org', '@type': 'SoftwareApplication', name: 'Your SaaS Name', applicationCategory: 'BusinessApplication', offers: { '@type': 'AggregateOffer', lowPrice: '0', highPrice: '99', priceCurrency: 'USD', offerCount: 3, }, }), }}/>FAQ Schema
Use the Markdoc FAQ node for automatic FAQ schema:
{% faq title="Frequently Asked Questions" items=[ {"question": "How do I get started?", "answer": "Sign up for a free account..."}, {"question": "Can I cancel anytime?", "answer": "Yes, you can cancel..."} ]/%}Article Schema
Add to blog posts:
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify({ '@context': 'https://schema.org', '@type': 'Article', headline: post.title, description: post.description, image: post.image, datePublished: post.publishedAt, dateModified: post.updatedAt, author: { '@type': 'Person', name: post.author, }, }), }}/>Robots.txt
The robots.txt is served by the TanStack Start server route at apps/web/src/routes/robots[.]txt.ts:
apps/web/src/routes/robots[.]txt.ts
import { createFileRoute } from '@tanstack/react-router';import appConfig from '#/config/app.config.ts';export const Route = createFileRoute('/robots.txt')({ server: { handlers: { GET: () => { const body = [ 'User-agent: *', 'Allow: /', 'Disallow: /dashboard/', 'Disallow: /admin/', 'Disallow: /api/', '', `Sitemap: ${appConfig.url}/sitemap.xml`, '', ].join('\n'); return new Response(body, { headers: { 'content-type': 'text/plain' }, }); }, }, },});The sitemap URL is derived from appConfig.url, so set that to your production domain.
Google Search Console
Verification
- Go to Google Search Console
- Add your property (URL prefix method)
- Choose verification method:
- HTML tag: Add a meta tag to the root head in
apps/web/src/lib/root-metadata.ts - HTML file: Upload to
apps/web/public/
- HTML tag: Add a meta tag to the root head in
// HTML tag verification — add to the meta array in getRootHead(){ name: 'google-site-verification', content: 'your-verification-code' },Submit Sitemap
After verification:
- Navigate to Sitemaps in Search Console
- Enter
sitemap.xmlin the input field - Click Submit
Google will crawl and index your sitemap within a few days.
Monitor Indexing
Check Search Console regularly for:
- Coverage: Pages indexed vs. excluded
- Enhancements: Structured data validation
- Core Web Vitals: Performance metrics
- Mobile Usability: Mobile-friendly issues
SEO Best Practices
Content Quality
Content quality matters more than technical SEO. Focus on:
- Helpful content: Solve problems your customers search for
- Unique value: Offer insights competitors don't have
- Regular updates: Keep content fresh and accurate
- Comprehensive coverage: Answer related questions
Keyword Strategy
| Element | Recommendation |
|---|---|
| Title | Primary keyword near the beginning |
| Description | Include keyword naturally, focus on click-through |
| H1 | One per page, include primary keyword |
| URL | Short, descriptive, include keyword |
| Content | Use variations naturally, don't stuff |
Image Optimization
<img src="/images/feature-screenshot.webp" alt="Dashboard showing project analytics with team activity" width={1200} height={630} loading="lazy"/>- Use WebP format for better compression
- Include descriptive alt text with keywords
- Use descriptive filenames (
project-dashboard.webpnotimg1.webp) - Size images appropriately for their display size
Internal Linking
Link between related content:
import { Link } from '@tanstack/react-router';// In your blog post about authentication<p> Learn more about{' '} <Link to="/docs/$slug" params={{ slug: 'authentication/setup' }}> setting up authentication </Link>{' '} in our documentation.</p>Page Speed
Makerkit is optimized for performance out of the box:
- Automatic code splitting via Vite + TanStack Router
- Responsive images with
<img>and modern formats (WebP/AVIF) - Self-hosted font optimization with
@fontsource-variable/*(loaded via CSS) - Server-rendered marketing pages
Check your scores with PageSpeed Insights.
Backlinks
Backlinks remain the strongest ranking factor. Strategies that work:
| Strategy | Effort | Impact |
|---|---|---|
| Create linkable content (guides, tools, research) | High | High |
| Guest posting on relevant blogs | Medium | Medium |
| Product directories (Product Hunt, etc.) | Low | Medium |
| Open source contributions | Medium | Medium |
| Podcast appearances | Medium | Medium |
Focus on quality over quantity. One link from a high-authority site beats dozens of low-quality links.
Timeline Expectations
SEO takes time. Typical timelines:
| Milestone | Timeline |
|---|---|
| Initial indexing | 1-2 weeks |
| Rankings for low-competition terms | 1-3 months |
| Rankings for medium-competition terms | 3-6 months |
| Rankings for high-competition terms | 6-12+ months |
Keep creating content and building backlinks. Results compound over time.
Related Resources
- Marketing Pages for building optimized landing pages
- CMS Setup for content marketing
- App Configuration for base URL and metadata settings