Spacerr
  • Features
  • Pricing
  • FAQ
  • Docs
Get Access
Spacerr
  • Introduction
  • Features
  • Tech Stack
  • Setup
  • Configuration
  • Agents
  • Database
  • Jobs
  • Admin
  • Settings
  • Billing
  • Storage
  • Email
  • Support
  • Localization
  • SEO
  • Analytics
  • UI And Navigation
  • Deploying To Production
  • Testing And QA
  • Troubleshooting

Search documentation

Search documentation pages.

Documentation/SEO

SEO

Spacerr includes SEO foundations for public routes, blog posts, social previews, crawlers, RSS readers, and AI discovery. Private product surfaces are kept out of the public index.

Lighthouse note: Lighthouse can sometimes show a missing meta description warning on Next.js pages because Next.js 15.2 introduced streaming support for async generateMetadata. For normal browsers, Next.js can send the page before metadata finishes, then append the resolved tags once metadata is ready. Lighthouse may inspect the initial response before those tags are visible. Next.js still gives HTML limited bots blocking metadata in the head through htmlLimitedBots, and JavaScript capable crawlers can read the completed DOM. Check the rendered page source before changing SEO code.

What It Includes

The SEO layer includes:

  • Route metadata with titles, descriptions, keywords, and canonical URLs.
  • Open Graph and Twitter card metadata.
  • JSON LD structured data.
  • Sitemap generation.
  • Robots rules.
  • RSS feed generation for blog posts.
  • llms.txt for AI crawler discovery.
  • Noindex metadata for private surfaces.
  • Blog post metadata and structured data.

Route Metadata

Route metadata lives in src/features/seo/get-route-metadata.ts.

typescript
export async function getRouteMetadata(
  routeKey: RouteSeoKey,
  canonical: string
): Promise<Metadata> {
  const t = await getTranslations("seo.routes")
  const title = t(`${routeKey}.title`)
  const description = t(`${routeKey}.description`)

  return {
    title,
    description,
    alternates: {
      canonical,
    },
    robots: {
      index: true,
      follow: true,
    },
  }
}

Route copy lives in src/i18n/en/seo.json. Change that file when you need a public route title, description, or keyword update.

json
{
  "routes": {
    "root": {
      "title": "AI SaaS Starter Kit for Next.js",
      "description": "Ship your AI SaaS in days, not months.",
      "keywords": "AI SaaS starter kit, Next.js AI template, Vercel AI SDK starter"
    }
  }
}

Structured Data

Structured data lives in src/features/seo/structured-data.tsx.

typescript
export function StructuredData({ description = SiteConfig.description }: StructuredDataProps) {
  const organization = {
    "@context": "https://schema.org",
    "@type": "Organization",
    name: SiteConfig.name,
    url: WebRoutes.product.withBaseUrl(),
    description,
  }

  return (
    <script
      type="application/ld+json"
      dangerouslySetInnerHTML={{
        __html: escapeJsonLd(JSON.stringify(organization)),
      }}
    />
  )
}

Use JSON LD for public pages that qualify for rich results. Keep private dashboard pages out of structured data.

Sitemap And Robots

The sitemap lives in src/app/sitemap.ts.

typescript
const staticRoutes: MetadataRoute.Sitemap = [
  {
    url: WebRoutes.product.withBaseUrl(),
    lastModified: now,
    changeFrequency: "weekly",
    priority: 1,
  },
]

const blogRoutes: MetadataRoute.Sitemap = posts.map((post) => ({
  url: getBlogPostCanonicalUrl(post.slug),
  lastModified: post.updatedAt,
  changeFrequency: "monthly",
  priority: 0.7,
}))

Robots rules live in src/app/robots.ts.

typescript
const PRIVATE_ROBOTS_PATHS = [
  "/api/",
  ...withExactAndTrailingSlash(WebRoutes.checkout.path),
  ...withExactAndTrailingSlash(WebRoutes.admin.path),
  ...withExactAndTrailingSlash(WebRoutes.dashboardLibrary.path),
] as const

Add public indexable routes to the sitemap. Add private or sensitive routes to robots disallow lists and noindex metadata.

RSS And AI Discovery

The RSS route lives in src/app/rss.xml/route.ts. It reads published blog posts and emits feed XML.

txt
/rss.xml

The AI discovery file lives in src/app/llms.txt/route.ts and is built by src/features/seo/llms-txt.ts.

txt
/llms.txt

Use llms.txt to explain public product surfaces to AI crawlers. Do not include private dashboard states, API routes, admin screens, or user data.

Blog SEO

Blog SEO lives with the blog feature.

txt
src/features/blog/utils/blog-post-seo.ts
src/features/blog/utils/blog-post-json-ld.tsx
src/app/rss.xml/route.ts
src/app/sitemap.ts

Blog posts use the stored slug and post fields to generate canonical URLs, Open Graph images, Twitter cards, RSS entries, and sitemap entries.

Where To Customize

Use these files first:

  • src/i18n/en/seo.json for public route metadata copy.
  • src/lib/site.config.ts for site name, description, author, social handles, and default product metadata.
  • src/features/seo/get-route-metadata.ts for route metadata assembly.
  • src/features/seo/metadata.ts for root metadata assembly.
  • src/features/seo/structured-data.tsx for JSON LD.
  • src/features/seo/noindex-metadata.ts for private page metadata.
  • src/app/sitemap.ts for sitemap entries.
  • src/app/robots.ts for crawler rules.
  • src/features/seo/llms-txt.ts for AI discovery content.
  • src/features/blog/utils/blog-post-seo.ts for blog metadata.
Blog SEO

Learn how blog metadata, RSS, sitemap, JSON LD, and social previews work in Spacerr.

Analytics

Learn how PostHog analytics, route tracking, billing events, and activity tracking are structured.

On this page
What It IncludesRoute MetadataStructured DataSitemap And RobotsRSS And AI DiscoveryBlog SEOWhere To Customize