> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getsabo.com/llms.txt
> Use this file to discover all available pages before exploring further.

# SEO

> Configure robots, sitemap, site metadata, and JSON‑LD for search engines.

<Info>
  This guide shows how to configure search engine indexing for your Sabo app: robots rules, a dynamic sitemap, site-wide metadata (Open Graph/Twitter), and JSON‑LD structured data.
</Info>

## What's included

* robots: `src/app/robots.ts`
* sitemap: `src/app/sitemap.ts`
* Site-wide metadata (Open Graph, Twitter, robots): `src/app/layout.tsx`
* JSON‑LD (structured data, Organization): `src/app/layout.tsx`

<Warning>
  Always use your production domain in SEO outputs (e.g., `https://yourdomain.com`), and restrict indexing in preview environments.
</Warning>

## Key concepts at a glance

* robots.txt: A plain-text file that tells crawlers which paths they may crawl or must avoid, and where your sitemap lives.
* sitemap.xml: A machine-readable list of your site’s URLs (and optional metadata like last modified). Helps crawlers discover content faster.
* Site-wide metadata (Open Graph, Twitter, robots): Meta tags that control link previews (title, description, image) and crawling behavior.
* JSON‑LD (structured data): Embedded JSON that describes your pages to search engines (e.g., Organization, BlogPosting) to enable rich results.

## robots.txt

File: `src/app/robots.ts`

```tsx src/app/robots.ts theme={null}
import { MetadataRoute } from "next";

export default function robots(): MetadataRoute.Robots {
  return {
    rules: [
      {
        userAgent: "*",
        allow: "/",
        disallow: [
          "/dashboard/*",
          "/sign-in",
          "/sign-up",
          "/forgot-password",
          "/reset-password",
        ],
      },
    ],
    sitemap: "https://demo.getsabo.com/sitemap.xml",
  };
}
```

<Steps>
  <Step title="Set your domain">
    Change the `sitemap` URL to your production domain.

    <Check>
      Visit `/robots.txt` and confirm the sitemap points to `https://yourdomain.com/sitemap.xml`.
    </Check>
  </Step>

  <Step title="Control indexing">
    Add items to `disallow` to prevent indexing of private/auth pages. For preview deploys, disallow everything or use a `noindex` robots policy via site metadata (see below).
  </Step>
</Steps>

## sitemap.xml

File: `src/app/sitemap.ts`

```tsx src/app/sitemap.ts theme={null}
import { MetadataRoute } from "next";
import { getAllPosts } from "@/lib/posts";
import { getChangelogEntries } from "@/lib/changelog";

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const baseUrl = "https://demo.getsabo.com";

  // Static routes (homepage, pricing, contact, etc.)
  const staticRoutes: MetadataRoute.Sitemap = [
    { url: baseUrl, lastModified: new Date(), changeFrequency: "weekly", priority: 1.0 },
    { url: `${baseUrl}/pricing`, lastModified: new Date(), changeFrequency: "weekly", priority: 0.9 },
    // ... more static routes
  ];

  // Dynamic blog posts from MDX files
  const posts = await getAllPosts();
  const blogRoutes: MetadataRoute.Sitemap = posts.map((post) => ({
    url: `${baseUrl}/blog/${post.slug}`,
    lastModified: new Date(post.date),
    changeFrequency: "monthly" as const, // TypeScript strict mode
    priority: 0.7,
  }));

  // Dynamic changelog entries
  const changelogEntries = await getChangelogEntries();
  const changelogRoutes: MetadataRoute.Sitemap = changelogEntries.map((entry) => ({
    url: `${baseUrl}/changelog/${entry.slug}`,
    lastModified: new Date(entry.releaseDate),
    changeFrequency: "monthly" as const,
    priority: 0.6,
  }));

  return [...staticRoutes, ...blogRoutes, ...changelogRoutes];
}
```

<Steps>
  <Step title="Set baseUrl">
    Replace `https://demo.getsabo.com` with your production domain (must match `robots.ts`).
  </Step>

  <Step title="Add dynamic routes">
    The example shows blog/changelog from MDX. Add other collections (products, docs, etc.) following the same pattern.
  </Step>

  <Step title="Test the output">
    <Check>
      Visit `/sitemap.xml` and verify all routes appear with correct timestamps.
    </Check>
  </Step>
</Steps>

<Note>
  The file is `sitemap.ts` but Next.js serves it as `/sitemap.xml` automatically. Use `as const` for TypeScript strict mode.
</Note>

## Site-wide metadata (Open Graph, Twitter, robots)

File: `src/app/layout.tsx`

```tsx src/app/layout.tsx theme={null}
import type { Metadata } from "next";

export const metadata: Metadata = {
  metadataBase: new URL("https://demo.getsabo.com"),
  title: {
    default: "Sabo - Modern Next.js SaaS Boilerplate",
    template: "%s | Sabo", // Page title | Site name
  },
  description: "A modern, production-ready Next.js SaaS boilerplate...",
  keywords: ["Next.js", "React", "TypeScript", "SaaS", "Boilerplate"],
  openGraph: {
    type: "website",
    locale: "en_US",
    url: "https://demo.getsabo.com",
    siteName: "Sabo",
    title: "Sabo - Modern Next.js SaaS Boilerplate",
    description: "A modern, production-ready Next.js SaaS boilerplate...",
    images: [{ url: "/og/homepage.png", width: 1200, height: 630 }],
  },
  twitter: {
    card: "summary_large_image",
    title: "Sabo - Modern Next.js SaaS Boilerplate",
    description: "A modern, production-ready Next.js SaaS boilerplate...",
    images: ["/og/homepage.png"],
    creator: "@sabo",
  },
  robots: {
    index: true,
    follow: true,
    googleBot: { index: true, follow: true },
  },
};
```

<Steps>
  <Step title="Set metadataBase">
    Update `metadataBase` to your production domain. This becomes the base for all relative URLs in meta tags.
  </Step>

  <Step title="Customize all fields">
    Update `title`, `description`, `keywords`, and social tags (`openGraph`, `twitter`) to match your brand.
  </Step>

  <Step title="Add OG images">
    Create 1200×630 images in `public/og/`. Keep titles/descriptions consistent across `openGraph` and `twitter`.
  </Step>
</Steps>

<Tip>
  For preview/staging environments, conditionally set `robots.index: false` to prevent accidental indexing.
</Tip>

## Route-level metadata

For specific pages, you can export page-level metadata (or `generateMetadata`) to override the defaults.

```tsx theme={null}
// src/app/blog/[slug]/page.tsx (example)
export const metadata = {
  title: "Post Title",
  description: "Short summary for this post.",
  alternates: { canonical: "/blog/post-slug" },
};
```

<Tip>
  Use `alternates.canonical` to avoid duplicate content when pages can be reached by multiple URLs.
</Tip>

## JSON‑LD (Structured data)

File: `src/app/layout.tsx` (Organization). You can add per‑page JSON‑LD (e.g., BlogPosting) in page files.

```tsx src/app/layout.tsx theme={null}
import type { Organization, WithContext } from "schema-dts";

// Inside RootLayout component
const jsonLd: WithContext<Organization> = {
  "@context": "https://schema.org",
  "@type": "Organization",
  name: "Sabo",
  url: "https://demo.getsabo.com",
  logo: "https://demo.getsabo.com/logo.png",
  description: "A modern, production-ready Next.js SaaS boilerplate...",
  sameAs: [], // Add social profiles: ["https://twitter.com/...", "https://linkedin.com/..."]
};

// In <head>
<script
  type="application/ld+json"
  dangerouslySetInnerHTML={{
    __html: JSON.stringify(jsonLd).replace(/</g, "\\u003c"),
  }}
/>
```

<Steps>
  <Step title="Customize fields">
    Update `name`, `url`, `logo`, and `description`. Add social profile URLs to `sameAs` array.
  </Step>

  <Step title="Add to layout">
    Place the script in `<head>` of your root layout. The `.replace(/</g, "\\u003c")` prevents script injection.
  </Step>
</Steps>

<Tip>
  For blog posts, add page-level JSON-LD with `@type: "BlogPosting"` including `author`, `datePublished`, and `image`.
</Tip>

## Verification and testing

<Steps>
  <Step title="Manual checks">
    * Visit `/robots.txt` and `/sitemap.xml`
    * View page source for meta tags and JSON‑LD
  </Step>

  <Step title="Tools">
    * Google Rich Results Test (structured data)
    * OpenGraph Preview tools (OG tags)
    * Twitter Card Validator
    * Search Console/Bing Webmaster for submission and coverage
  </Step>

  <Step title="Common issues">
    * Wrong domain in `metadataBase` or sitemap
    * Preview builds accidentally indexed (set `noindex`)
    * OG images missing or wrong path
  </Step>
</Steps>

<Check>
  Deployed site exposes correct robots and sitemap, has consistent OG/Twitter meta, and validates in rich results tests.
</Check>
