The boilerplate includes comprehensive SEO tools including dynamic sitemap generation, robots.txt configuration, structured data (JSON-LD), and metadata utilities for optimal search engine visibility.

Dynamic Sitemap

Auto-generated sitemap with static and CMS pages.

Structured Data

JSON-LD schemas for Organization, Product, FAQ, and more.

robots.txt

Dynamic robots.txt with protected routes excluded. Fully blocked when NEXT_PUBLIC_INDEXABLE is not true.

OpenGraph & Twitter

Social media metadata for rich link previews.

Viewport & Theme

Mobile-optimized viewport with light/dark theme colors.

PWA Manifest

Web app manifest for installability and app icons.

Resource Preloading

DNS prefetch and preconnect for faster page loads.

Locale Proxy

Auto locale detection with SEO-friendly redirects (Next.js 16+).

Sitemap Generation

The sitemap is automatically generated at /sitemap.xml and includes both static pages and dynamic CMS pages. It's built using Next.js Metadata API.

Page Type Priority Change Frequency
Homepage (/) 1.0 Daily
Pricing (/pricing) 0.8 Weekly
Blog index (/blog) 0.8 Daily
Contact (/contact) 0.7 Monthly
CMS pages (dynamic, excluding legal/blog) 0.7 Weekly
Blog posts (dynamic, blog/*) 0.7 Weekly
Changelog (/changelog) 0.6 Weekly
Affiliates (/affiliates, only when AFFILIATES_ENABLED=true) 0.6 Monthly
Legal pages (/terms, /privacy, /privacy-choices, /legal) 0.5 Monthly
Non-default locales

Pages for non-default locales are emitted with priority multiplied by 0.9 (e.g. an English-default site emits French URLs at 0.9, 0.72, 0.63, 0.54…). Configured in app/sitemap.ts.

Freshness

The sitemap reads the database, which Next.js does not treat as a dynamic API — so it is revalidated on an hourly ISR window (export const revalidate = 3600). Without that it would be rendered once at build time and frozen, and content published after the last deploy would never appear.

Multi-locale sitemap & hreflang

Every sitemap entry carries the full hreflang cluster for its page — one <url> per locale, each linking to all the others plus itself and an x-default, following Google’s localized-versions guidance. The same cluster is emitted in each page’s <head> via alternates.languages.

For CMS pages and blog posts, a locale is included only when all three hold:

  • Published in that locale — per-locale, resolved without cross-language fallback. A page published only in French is absent from the English sitemap and is not listed as an English alternate.
  • Not flagged noindex for that locale.
  • Not canonicalized elsewhere — if you set a custom canonical pointing at a different URL, that locale is dropped rather than advertised in a sitemap that contradicts its own canonical tag.

Slugs are shared across locales (/fr-FR/about and /en-US/about are the same row), so an entry differs only by its locale prefix. Slugs owned by a dedicated route — terms, privacy, privacy-choices, legal, pricing, contact, blog, changelog, affiliates — are excluded from the CMS portion of the sitemap: a static route always wins the match, so a CMS page using one of those slugs is unreachable.

Adding a locale to an existing site

Publication is per-locale with no cross-language fallback, so existing CMS rows do not automatically become published in a newly added locale — they stay out of that locale’s sitemap until you publish them. This is intentional: it stops a page going live in a language it was never translated into. See Internationalization → Adding a New Locale.

When NEXT_PUBLIC_INDEXABLE is not true, the sitemap is served empty. robots.txt already blocks every crawler in that case, but an empty sitemap also avoids handing a full URL inventory to anything that ignores robots.txt.

robots.txt Configuration

The /robots.txt file is dynamically generated based on the NEXT_PUBLIC_INDEXABLE environment variable (configured as appConfig.isIndexable). When indexing is disabled (the default), all crawlers are blocked with Disallow: /. When enabled (NEXT_PUBLIC_INDEXABLE=true), the following private routes are excluded from crawling:

  • /api/* - All API routes
  • /private-dashboard/* - User dashboard
  • /admin-dashboard/* - Admin panel
  • /my-account/* - Account settings
  • /login, /register, /callback - Auth pages (/register has no implementation today — auth is magic-link only; this entry is defensive)
  • /workspace/* - Workspace pages
Staging / Dev environments

Leave NEXT_PUBLIC_INDEXABLE unset or set to false on staging and development environments to prevent accidental indexing. The defaultMetadata robots directive and robots.txt both respect this flag.

Structured Data (JSON-LD)

The boilerplate provides helper functions to generate structured data schemas that help search engines understand your content. Available in lib/seo/index.ts:

Function Schema Type Use Case
generateOrganizationSchema() Organization Company info, logo, social links
generateWebsiteSchema() WebSite Site name, search action
generateProductSchema() Product Pricing plans with offers
generateFAQSchema() FAQPage FAQ sections for rich snippets
generateSoftwareApplicationSchema() SoftwareApplication SaaS product with ratings
generateBreadcrumbSchema() BreadcrumbList Navigation breadcrumbs
generateWebPageSchema() WebPage General page metadata
generateContactPageSchema() ContactPage Contact page with organization info

Default Metadata

The defaultMetadata object provides base SEO configuration that can be extended per-page:

  • Title Template - Uses %s | Your App pattern for consistent titles
  • Description - Default description from config/app.ts
  • OpenGraph - Site name, type, locale with alternate locale
  • Twitter Card - Summary with large image
  • Robots - Index and follow controlled by NEXT_PUBLIC_INDEXABLE env variable, with GoogleBot optimizations

SEO Utility Functions

Function Description
getCanonicalUrl(path) Generates full canonical URL for a given path
getAlternateLanguages(path, availableLocales?) Returns hreflang alternate URLs plus x-default. Omit the second argument for routes that exist in every locale; pass the available locales for content that does not (CMS pages, blog posts). Returns an empty map when no locale qualifies, so callers can omit alternates.languages entirely.
getPublishedLocales(page) (lib/cms/seo.ts) Locales a CMS row is published in. Use for UI — language switcher, cross-locale links.
getIndexableLocales(page, options) (lib/cms/seo.ts) Published and not noindex and not canonicalized elsewhere. Use for crawler surfaces — hreflang and the sitemap.
jsonLdScriptProps(schema) Returns props for embedding JSON-LD in a script tag

Viewport & Mobile Optimization

The root layout includes a comprehensive viewport configuration for optimal mobile experience:

  • Device Width - Responsive width matching device screen
  • Initial Scale - Set to 1 for proper initial zoom
  • Maximum Scale - Set to 5 for accessibility (allows zooming)
  • Theme Color - Automatic light/dark mode browser UI colors

PWA Manifest

The dynamic manifest at app/manifest.ts (Next.js Metadata API) is the canonical source — it reads appConfig at build time so name, theme color, and icons stay in sync with the rest of the project. A static public/manifest.json may also exist for legacy fallback. PWA features it enables:

  • Installability - Users can install the app on their devices
  • Standalone Mode - App runs without browser UI
  • App Icons - SVG and PNG icons for various contexts
  • Theme Colors - Consistent branding across platforms

Resource Preloading

The frontend layout includes DNS prefetch and preconnect hints for critical resources:

  • Google Fonts - DNS prefetch and preconnect for faster font loading
  • Supabase - Conditional preconnect when configured
  • Google Tag Manager - Conditional preconnect when GTM is enabled

Locale Proxy

The proxy.ts handles automatic locale detection and SEO-friendly redirects (Next.js 16+ convention):

  • Accept-Language Detection - Automatically detects user's preferred language
  • Cookie Persistence - Remembers user's locale preference for 1 year
  • 308 Permanent Redirect - SEO-friendly redirects for non-localized URLs
  • Excluded Paths - Static files and API routes bypass locale routing
SEO Configuration

Base SEO settings are configured in config/app.ts under the seo object. This includes default title, title template, and OpenGraph/Twitter settings.

Required Image Files

You need to create the following image files in public/: favicon.ico (32x32), apple-touch-icon.png (180x180), and og-image.png (1200x630). SVG favicons are already provided.