This guide helps you improve SEO in a Next.js 15 project using the App Router. You'll use built-in features like the metadata API, dynamic routes, and structured data—no CMS required.
Next.js 15 provides a built-in metadata
API for handling SEO metadata.
app/layout.tsx
import type { Metadata } from "next";
export const metadata: Metadata = {
metadataBase: new URL("https://bmed.vercel.app"),
title: {
default: "BounaderMedRafik",
template: "%s | BounaderMedRafik",
},
description: "Explore my portfolio to discover my passions and skills.",
openGraph: {
title: "Mohamed Rafik",
description: "A glimpse into my world.",
locale: "en_DZ",
type: "website",
url: "https://bmed.vercel.app",
images: [
{
url: "https://example.com/og.jpg",
width: 800,
height: 600,
},
],
},
keywords: ["portfolio", "web dev", "Next.js", "Rafik", "coding"],
};
Use generateMetadata
inside dynamic routes like /thoughts/[id]
to create per-page meta tags.
app/thoughts/[id]/page.tsx
import { thoughts } from "@/db/thoughts";
import type { Metadata } from "next";
export async function generateMetadata({ params }: { params: { id: string } }): Promise<Metadata> {
const thought = thoughts.find((t) => t.id === params.id);
if (!thought) return { title: "Not Found" };
return {
title: thought.title,
description: thought.summary || "Read this thought piece.",
openGraph: {
title: thought.title,
description: thought.summary,
type: "article",
images: [{ url: thought.image }],
},
};
}
Use the App Router API to generate a sitemap dynamically.
app/sitemap.ts
import { DOMAIN } from "@/db/data";
import { thoughts } from "@/db/thoughts";
export default async function sitemap() {
const thoughtUrls = thoughts.map((t) => ({
url: https://www.yourblogwebsite.com,
lastModified: t.date,
}));
return [
{ url: DOMAIN, lastModified: new Date() },
...thoughtUrls,
];
}
A robots.txt file tells crawlers which pages they can access.
app/robots.ts
import { MetadataRoute } from "next";
import { DOMAIN } from "@/db/data";
export default function robot(): MetadataRoute.Robots {
return {
rules: [
{
userAgent: "*",
allow: "/",
disallow: [],
},
],
sitemap: https://www.yourblogwebsite.com,
};
}
Structured data helps search engines understand your content better.
app/thoughts/[id]/page.tsx
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify({
"@context": "https://schema.org",
"@type": "BlogPosting",
headline: thought.title,
datePublished: thought.date,
description: thought.summary,
image: thought.image,
author: {
"@type": "Person",
name: "Mohamed Rafik",
},
}),
}}
/>
Feature | Status |
---|---|
metadata API | Implemented |
Per-page metadata | Recommended |
Sitemap | Implemented |
Robots.txt | Implemented |
Open Graph meta | Implemented |
Structured data (JSON-LD) | Recommended |