---
name: add-seo-metadata-and-sitemap
description: Adds search basics to a Next.js site, with a title and description per page, Open Graph share images, sitemap.ts, robots.ts, LocalBusiness structured data and a favicon. Use when the user wants to show up on Google, improve SEO, fix link previews, or add a sitemap or meta tags.
license: CC-BY-4.0
metadata:
  category: frontend
  tags: "seo,nextjs,website"
  author: johnny-bravo
  version: "1.1"
  updated: "2026-10-31"
  status: published
---

# Add SEO metadata and a sitemap

This skill adds the basic tags and files search engines and chat apps read. To find what is wrong on a live site, use `audit-site-seo` first.

## When to use
- A Next.js App Router site is built and needs to be findable and share nicely on social media and chat apps.
- Link previews show no image or the wrong title.
- Google Search Console asks for a sitemap.
- An SEO audit found missing titles, descriptions, a sitemap or structured data.

## Handoff
- Reads: `docs/website-brief.md` (page list, business details) and `lib/site.ts`.
- Writes: `metadata` exports in `app/layout.tsx` and each `page.tsx`, `app/sitemap.ts`, `app/robots.ts`, `app/opengraph-image.tsx` (or `.png`), `app/icon.png`, and LocalBusiness JSON-LD in `app/layout.tsx`.

## Steps
1. Site-wide defaults in `app/layout.tsx`:
   ```ts
   export const metadata: Metadata = {
     metadataBase: new URL(site.url),            // e.g. https://www.example.com.au
     title: { default: site.name, template: `%s | ${site.name}` },
     description: "One sentence saying what you do and where.",
     openGraph: { type: "website", siteName: site.name, locale: "en_AU" },
   };
   ```
2. Every page exports its own `metadata` with a unique `title` (under about 60 characters) and `description` (about 150 characters) written for people, using the words customers search for (service + suburb or city).
3. Share image: `app/opengraph-image.tsx` using `ImageResponse` from `next/og` (1200×630, business name and tagline on the brand colour), or drop a 1200×630 `app/opengraph-image.png`. Add `alt` via `export const alt = "..."`.
4. Icons: put `app/icon.png` (square, at least 512×512) and `app/apple-icon.png` in `app/`. Next.js adds the `<link>` tags. Remove the default `favicon.ico` if it is still the Next.js logo.
5. `app/sitemap.ts`:
   ```ts
   import type { MetadataRoute } from "next";
   export default function sitemap(): MetadataRoute.Sitemap {
     return ["", "/about", "/services", "/contact", "/privacy"].map((p) => ({ url: `${site.url}${p}`, lastModified: new Date() }));
   }
   ```
6. `app/robots.ts`: allow everything in production and point to the sitemap: `{ rules: { userAgent: "*", allow: "/" }, sitemap: \`${site.url}/sitemap.xml\` }`.
7. Structured data: in `app/layout.tsx` render `<script type="application/ld+json">` with a `LocalBusiness` (or a more specific type such as `Plumber`) object: `name`, `url`, `telephone`, `address` (`PostalAddress`), `areaServed`, `openingHours`. Only include facts that are also visible on the page.
8. Check: `npm run build && npm start`, then open `/sitemap.xml`, `/robots.txt` and `/opengraph-image`. Paste a page into Google's Rich Results Test, and preview a link in a chat app after deploying.

## Gotchas
- Set `metadataBase`, or Open Graph image URLs come out relative and previews break.
- Don't block the live site: no `noindex` and no `Disallow: /` in production. Vercel already sends `X-Robots-Tag: noindex` on preview deployments (not on a custom domain assigned to a branch).
- One `<h1>` per page that matches what the page is about; titles and headings beat keyword stuffing.
- Use a single canonical host (www or not) and redirect the other one; this is set up in the launch step.
- Structured data that claims things not on the page (fake reviews, wrong hours) can get it ignored.

## Safety
- Keep the Search Console verification token and any analytics keys in environment variables, not in public code you share.
- Structured data is public. Do not put private details (a home address, a personal phone) in it that are not already on the page.

## Works with other stacks
On Astro, Nuxt, SvelteKit or WordPress the same pieces apply: per-page titles, a sitemap, robots rules, a share image and JSON-LD. Only the file names change. With WordPress, a plugin such as Yoast or an equivalent does most of it.

## Done when
- Every page has a unique title and description.
- `/sitemap.xml`, `/robots.txt` and the share image load on the live site.
- The Rich Results Test shows valid structured data, and a shared link shows the right preview.

## Credits
Topic coverage informed by addyosmani/web-quality-skills (https://github.com/addyosmani/web-quality-skills, MIT © 2026 Addy Osmani). The text here is our own.

## Example prompt
"Get my site showing up on Google: add page titles, a sitemap, and make the link preview show our logo when I share it."
