SEO
The template includes a scalable SEO architecture built on the Next.js App Router metadata API.
Features
- Global SEO configuration
- Page-specific metadata
- Dynamic metadata generation
- Open Graph support
- Twitter cards
- Structured data (JSON-LD)
- Sitemap generation
- Robots.txt generation
- Blog SEO
- Docs SEO
- Dynamic route SEO
Folder Structure
src/
├── app/
│ ├── layout.tsx
│ ├── sitemap.ts
│ ├── robots.ts
│ ├── pricing/
│ │ └── page.tsx
│ ├── blog/
│ │ └── [slug]/
│ │ └── page.tsx
│ └── docs/
│ └── [slug]/
│ └── page.tsx
│
├── content/
│ ├── marketing/
│ │ ├── home.ts
│ │ ├── pricing.ts
│ │ └── features.ts
│ ├── blog/
│ ├── docs/
│ └── seo/
│ └── global.ts
│
├── lib/
│ └── seo/
│ ├── generateMetadata.ts
│ └── schema.ts
│
└── components/
└── seo/
└── StructuredData.tsx
Global SEO Configuration
Global SEO settings live in a single file.
content/seo/global.ts
export const SEO_CONFIG = {
/** Site name used in titles, metadata, and Open Graph branding. */
siteName: "Pulse AI",
/** Base URL. Falls back to localhost for local development. */
siteUrl: process.env.NEXT_PUBLIC_SITE_URL ?? "http://localhost:3000",
/** Default title shown in browser tabs and search results. */
title: "Pulse AI",
/** Meta description shown in search results. */
description: "Premium Next.js SAAS Template - Built by launchpadnext",
/** Default Open Graph image. Recommended size: 1200x630px. */
ogImage: "/og/default.png",
/** Twitter handle for Twitter Card metadata. */
twitterHandle: "@launchpadnext",
/** SEO keywords for internal indexing or non-Google engines. */
keywords: ["Next.js", "SaaS", "AI", "Boilerplate", "Templates"],
author: "launchpadnext",
creator: "launchpadnext",
twitter: "@launchpadnext",
/** Default locale for Open Graph and international SEO. */
locale: "en_US",
};Root Layout Metadata
Global metadata is defined in app/layout.tsx and acts as a fallback for all pages.
app/layout.tsx
import type { Metadata } from "next";
import { SEO_CONFIG } from "@/content/seo/global";
export const metadata: Metadata = {
metadataBase: new URL(SEO_CONFIG.siteUrl),
title: {
default: SEO_CONFIG.title,
template: `%s | ${SEO_CONFIG.siteName}`,
},
description: SEO_CONFIG.description,
openGraph: {
title: SEO_CONFIG.title,
description: SEO_CONFIG.description,
images: [SEO_CONFIG.ogImage],
siteName: SEO_CONFIG.siteName,
type: "website",
},
twitter: {
card: "summary_large_image",
title: SEO_CONFIG.title,
description: SEO_CONFIG.description,
images: [SEO_CONFIG.ogImage],
},
};Page SEO
Each page defines its own metadata alongside its content in a single file.
content/marketing/pricing.ts
export const PRICING_CONTENT = {
seo: {
title: "Pricing",
description: "Simple pricing plans for startups and growing teams.",
keywords: ["pricing", "saas pricing", "subscription plans"],
},
// Main content
hero: {
heading: "Simple Pricing",
},
};app/pricing/page.tsx
import { Metadata } from "next";
import { PRICING_CONTENT } from "@/content/marketing/pricing";
export const metadata: Metadata = {
title: PRICING_CONTENT.seo.title,
description: PRICING_CONTENT.seo.description,
keywords: PRICING_CONTENT.seo.keywords,
};
export default function PricingPage() {
return <Pricing />;
}Reusable Metadata Generator
For larger projects, use a helper to avoid repeating Open Graph and Twitter metadata on every page.
lib/seo/generateMetadata.ts
import { Metadata } from "next";
interface SeoProps {
title: string;
description: string;
keywords?: string[];
image?: string;
}
export function generateMetadata({
title,
description,
keywords,
image,
}: SeoProps): Metadata {
return {
title,
description,
keywords,
openGraph: {
title,
description,
images: image ? [image] : undefined,
},
twitter: {
card: "summary_large_image",
title,
description,
images: image ? [image] : undefined,
},
};
}Usage:
import { generateMetadata } from "@/lib/seo/generateMetadata";
import { PRICING_CONTENT } from "@/content/marketing/pricing";
export const metadata = generateMetadata(PRICING_CONTENT.seo);Dynamic Route SEO
Next.js supports metadata generation at runtime using generateMetadata. This is useful for blog posts, documentation pages, integrations, changelogs, and case studies.
Blog SEO
Blog content:
export const BLOG_POST = {
slug: "best-ai-chatbots",
title: "Best AI Chatbots",
description: "Discover the best AI chatbots available today.",
image: "/blog/best-ai-chatbots.png",
publishedAt: "2026-01-10",
};app/blog/[slug]/page.tsx
import { generateMetadata } from "@/lib/seo/generateMetadata";
export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const post = await getPostBySlug(slug);
return generateMetadata({
title: post.title,
description: post.description,
image: post.image,
});
}
export default async function BlogPage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const post = await getPostBySlug(slug);
return <BlogPost post={post} />;
}Docs SEO
app/docs/[slug]/page.tsx
import { generateMetadata } from "@/lib/seo/generateMetadata";
export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const doc = await getDocBySlug(slug);
return generateMetadata({
title: doc.title,
description: doc.description,
});
}
export default async function DocsPage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const doc = await getDocBySlug(slug);
return <DocsContent doc={doc} />;
}Structured Data
Structured data helps search engines better understand your content.
components/seo/StructuredData.tsx
interface StructuredDataProps {
data: Record<string, unknown>;
}
export default function StructuredData({ data }: StructuredDataProps) {
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(data) }}
/>
);
}lib/seo/schema.ts
export function softwareSchema() {
return {
"@context": "https://schema.org",
"@type": "SoftwareApplication",
name: "Pulse AI",
applicationCategory: "BusinessApplication",
operatingSystem: "Web",
};
}Usage:
import StructuredData from "@/components/seo/StructuredData";
import { softwareSchema } from "@/lib/seo/schema";
<StructuredData data={softwareSchema()} />;Sitemap
The sitemap is automatically generated at build time.
app/sitemap.ts
import { MetadataRoute } from "next";
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const posts = await getAllPosts();
return [
{ url: "https://pulseai.com", priority: 1 },
{ url: "https://pulseai.com/pricing", priority: 0.9 },
...posts.map((post) => ({
url: `https://pulseai.com/blog/${post.slug}`,
priority: 0.7,
})),
];
}Robots
app/robots.ts
import { MetadataRoute } from "next";
export default function robots(): MetadataRoute.Robots {
return {
rules: {
userAgent: "*",
allow: "/",
},
sitemap: "https://pulseai.com/sitemap.xml",
};
}Recommended Content Structure
content/
├── marketing/
│ ├── home.ts
│ ├── pricing.ts
│ ├── features.ts
│ ├── about.ts
│ └── contact.ts
├── blog/
├── docs/
├── legal/
└── seo/
└── global.ts
Each page exports both its content and SEO configuration from a single file, keeping metadata, copy, and page config together:
// content/marketing/pricing.ts
export const PRICING_CONTENT = {
seo: {
title: "Pricing",
description: "Simple pricing plans for startups and growing teams.",
keywords: ["Next.js 16 Template", "SaaS"],
ogImage: "/images/og/pricing.png",
},
// ...page content
};Best Practices
- Keep SEO content close to page content
- Always provide a unique title and description per page
- Use Open Graph images for important pages
- Add structured data for blogs and SaaS products
- Generate metadata dynamically for all dynamic routes
- Keep titles under 60 characters where possible
- Keep descriptions between 120–160 characters
- Include relevant keywords naturally within page content
- Ensure all important pages are included in the sitemap