SEO + OG
Metadata + sitemap + robots.
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
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:
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
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
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:
export const metadata: Metadata = {robots: { index: false, follow: false },}
Quick check
Try it
For chapter 2's assignment, complete the SEO + OG pass on your scaffolded web app:
- Add a metadata object to
apps/web/app/(marketing)/page.tsx. - Add
app/sitemap.ts+app/robots.ts. - Add a static OG image (or generate via
next/og). - Deploy to a public URL (Vercel, your VPS) and run the OG tester.
- 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