SEO + OG

Metadata + sitemap + robots.

6 mineasy

SEO is "how do search engines find you"; OG is "how does your link preview when shared". Both are metadata. Next.js makes them one-liner additions. This lesson is the checklist.

The page metadata object

apps/web/app/(marketing)/page.tsx
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Acme — Run your retail business from one place',
description: 'POS, inventory, reports for shops with 1 to 50 outlets.',
openGraph: {
title: 'Acme — Run your retail business from one place',
description: 'POS, inventory, reports for shops with 1 to 50 outlets.',
url: 'https://acme.com',
siteName: 'Acme',
images: [{ url: 'https://acme.com/og.png', width: 1200, height: 630 }],
type: 'website',
},
twitter: {
card: 'summary_large_image',
title: 'Acme — Run your retail business',
description: 'POS, inventory, reports.',
images: ['https://acme.com/og.png'],
},
}
export default function HomePage() { /* ... */ }

Next.js renders these into <meta> tags at build / request time. No work beyond declaring the object.

The shareable OG image

1200 × 630 PNG. Shows when someone shares your link in Slack / Twitter / WhatsApp. Build it once with Figma + export PNG.

Or generate dynamically with next/og:

apps/web/app/og/route.tsx
import { ImageResponse } from 'next/og'
export async function GET() {
return new ImageResponse(
<div tw="flex w-full h-full bg-black text-white items-center justify-center">
<div tw="text-7xl font-bold">Acme</div>
</div>,
{ width: 1200, height: 630 },
)
}

Now https://acme.com/og renders a dynamic OG image — useful when you want page-specific previews (blog post titles, user profiles).

sitemap.xml

apps/web/app/sitemap.ts
import type { MetadataRoute } from 'next'
export default function sitemap(): MetadataRoute.Sitemap {
return [
{ url: 'https://acme.com', priority: 1.0, changeFrequency: 'weekly' },
{ url: 'https://acme.com/pricing', priority: 0.9, changeFrequency: 'monthly' },
{ url: 'https://acme.com/blog', priority: 0.8, changeFrequency: 'weekly' },
// For dynamic pages, fetch from the API and map them
]
}

Available at /sitemap.xml. Google reads it to discover every public URL. For dynamic blog posts, fetch from your API and map each post to an entry.

robots.txt

apps/web/app/robots.ts
import type { MetadataRoute } from 'next'
export default function robots(): MetadataRoute.Robots {
return {
rules: { userAgent: '*', allow: '/', disallow: '/dashboard/' },
sitemap: 'https://acme.com/sitemap.xml',
}
}

Tells crawlers what to index. Always disallow /dashboard and similar logged-in surfaces — they'd 404 for the bot anyway.

For the admin app

apps/admin should NEVER be indexed. Add a global robots meta in its root layout:

apps/admin/app/layout.tsx
export const metadata: Metadata = {
robots: { index: false, follow: false },
}
Validate with the Facebook OG Debugger. Visit developers.facebook.com/tools/debug, paste your URL. You see exactly what the preview will look like. Same for Twitter Card Validator.

Quick check

You ship your landing page with OG metadata. You share the link in Slack but the preview doesn't render. What's the most likely cause?

Try it

For chapter 2's assignment, complete the SEO + OG pass on your scaffolded web app:

  1. Add a metadata object to apps/web/app/(marketing)/page.tsx.
  2. Add app/sitemap.ts + app/robots.ts.
  3. Add a static OG image (or generate via next/og).
  4. Deploy to a public URL (Vercel, your VPS) and run the OG tester.
  5. Run Lighthouse — SEO score should be 100.

Paste the OG preview + Lighthouse SEO in notes.md.

What's next

Chapter 3 — The User Dashboard. Signup, login, the logged-in surface. The customer's home base.

Spot a typo? Have an idea?

Help us improve this lesson. One click opens a GitHub issue with the lesson URL pre-filled — suggest clearer wording, report a bug, or request more depth. The course keeps improving thanks to learners like you.

Suggest an improvement on GitHub