Skip to main content

SEO in SaaS Boilerplates: What Most Get Wrong in 2026

·StarterPick Team
seonext-seositemapboilerplate2026

TL;DR

Most boilerplates focus on the app — auth, billing, dashboard. SEO for the marketing site is often incomplete or wrong. Key gaps: missing structured data, incorrect canonical URLs, no sitemap generation, poor meta descriptions, and app routes leaking into search indexes. These are fixable in a day but have a large long-term impact.

What SaaS Boilerplates Get Right

A well-configured Next.js boilerplate ships with:

// app/layout.tsx — global metadata
export const metadata: Metadata = {
  title: {
    default: 'YourSaaS — Short Description',
    template: '%s | YourSaaS',
  },
  description: 'Compelling 150-160 character description with primary keyword.',
  openGraph: {
    type: 'website',
    locale: 'en_US',
    url: 'https://yoursaas.com',
    siteName: 'YourSaaS',
  },
  twitter: {
    card: 'summary_large_image',
    creator: '@yoursaas',
  },
};

This is the minimum. Most boilerplates stop here.


What Boilerplates Get Wrong

1. No Canonical URLs

Without canonicals, duplicate content (HTTP vs HTTPS, www vs non-www, trailing slashes) hurts rankings:

// app/layout.tsx — add canonical
export const metadata: Metadata = {
  alternates: {
    canonical: 'https://yoursaas.com',
  },
};

// app/blog/[slug]/page.tsx — per-page canonical
export async function generateMetadata({ params }): Promise<Metadata> {
  return {
    alternates: {
      canonical: `https://yoursaas.com/blog/${params.slug}`,
    },
  };
}

2. Missing Structured Data

Rich results (star ratings, FAQ accordions, breadcrumbs) require JSON-LD:

// components/json-ld.tsx
export function ArticleJsonLd({ post }: { post: Post }) {
  const jsonLd = {
    '@context': 'https://schema.org',
    '@type': 'Article',
    headline: post.title,
    description: post.description,
    datePublished: post.publishedAt,
    dateModified: post.updatedAt,
    author: {
      '@type': 'Organization',
      name: 'YourSaaS',
      url: 'https://yoursaas.com',
    },
    publisher: {
      '@type': 'Organization',
      name: 'YourSaaS',
      logo: { '@type': 'ImageObject', url: 'https://yoursaas.com/logo.png' },
    },
    image: post.ogImage ?? 'https://yoursaas.com/og-default.png',
  };

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

// For SaaS homepage — SoftwareApplication schema
export function SoftwareJsonLd() {
  const jsonLd = {
    '@context': 'https://schema.org',
    '@type': 'SoftwareApplication',
    name: 'YourSaaS',
    applicationCategory: 'BusinessApplication',
    operatingSystem: 'Web',
    offers: {
      '@type': 'Offer',
      price: '29',
      priceCurrency: 'USD',
    },
    aggregateRating: {
      '@type': 'AggregateRating',
      ratingValue: '4.8',
      ratingCount: '120',
    },
  };

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

3. App Routes in Search Index

Your /dashboard, /settings, and /api/* routes should not be indexed:

# public/robots.txt
User-agent: *
Allow: /
Disallow: /dashboard/
Disallow: /settings/
Disallow: /api/
Disallow: /auth/

Sitemap: https://yoursaas.com/sitemap.xml
// Or via Next.js metadata
export const metadata: Metadata = {
  robots: {
    index: false,
    follow: false,
  },
};

// Apply to app layout
// app/(app)/layout.tsx — dashboard routes
export const metadata: Metadata = {
  robots: 'noindex, nofollow',
};

4. No Sitemap Generation

A sitemap tells search engines which pages to crawl. Dynamic pages (blog, comparison pages) need dynamic sitemaps:

// app/sitemap.ts
import { MetadataRoute } from 'next';

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const posts = await prisma.post.findMany({
    where: { published: true },
    select: { slug: true, updatedAt: true },
  });

  const blogUrls = posts.map((post) => ({
    url: `https://yoursaas.com/blog/${post.slug}`,
    lastModified: post.updatedAt,
    changeFrequency: 'weekly' as const,
    priority: 0.7,
  }));

  return [
    {
      url: 'https://yoursaas.com',
      lastModified: new Date(),
      changeFrequency: 'monthly',
      priority: 1.0,
    },
    {
      url: 'https://yoursaas.com/pricing',
      lastModified: new Date(),
      changeFrequency: 'monthly',
      priority: 0.9,
    },
    ...blogUrls,
  ];
}

5. Poor OG Image Generation

Social previews drive link click-through. Boilerplates often have a static OG image that's the same for every page:

// app/blog/[slug]/opengraph-image.tsx — Dynamic OG images
import { ImageResponse } from 'next/og';

export const size = { width: 1200, height: 630 };
export const contentType = 'image/png';

export default async function Image({ params }: { params: { slug: string } }) {
  const post = await getPost(params.slug);

  return new ImageResponse(
    (
      <div
        style={{
          background: 'linear-gradient(135deg, #1a1a2e 0%, #16213e 100%)',
          width: '100%',
          height: '100%',
          display: 'flex',
          flexDirection: 'column',
          padding: '80px',
          justifyContent: 'center',
        }}
      >
        <div style={{ fontSize: 48, fontWeight: 700, color: 'white', marginBottom: 24 }}>
          {post.title}
        </div>
        <div style={{ fontSize: 24, color: '#94a3b8' }}>
          {post.description}
        </div>
        <div style={{ position: 'absolute', bottom: 40, right: 80, color: '#64748b', fontSize: 20 }}>
          YourSaaS.com
        </div>
      </div>
    ),
    size
  );
}

The Marketing Site SEO Checklist

Quick audit for any boilerplate:

[ ] Title tag: under 60 chars, includes primary keyword
[ ] Meta description: 150-160 chars, compelling, includes keyword
[ ] H1: one per page, matches page intent
[ ] Canonical URL: set on every page
[ ] robots.txt: excludes app routes
[ ] sitemap.xml: auto-generated, submitted to Google Search Console
[ ] OG tags: title, description, image for all shareable pages
[ ] JSON-LD: Article (blog), SoftwareApplication (homepage)
[ ] Core Web Vitals: LCP < 2.5s, FID < 100ms, CLS < 0.1
[ ] Internal links: blog posts link to pricing and product pages
[ ] Alt text: all images have descriptive alt text

Boilerplate SEO Out of the Box

BoilerplateMeta TagsSitemapStructured Datarobots.txt
ShipFast
MakerkitPartial
T3 Stack
AstroWind
Open SaaS

Find SEO-optimized boilerplates on StarterPick.

Check out this boilerplate

View Next.js on StarterPick →

Comments