# Dynamic routes use generateMetadata for OG tags

- **Pattern:** `ab-001820` (`marketing-social-sharing.share-infrastructure.dynamic-og-for-dynamic-content`)
- **Severity:** critical
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-001820
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-001820)

## Why it matters

A static `metadata` export on a dynamic route (`[slug]`, `[id]`, `[username]`) applies identical OG tags to every instance of that route. Every blog post shares the same title, every product page shows the same description, and every user profile presents the same image in social previews. Per ogp.me, og tags are the mechanism social platforms use to generate link preview cards; when all instances of a dynamic route return the same tags, the platform cannot differentiate between content pieces. This is the most common and highest-impact social sharing gap in AI-built projects: content that was individually created gets collectively presented as one undifferentiated link, eliminating per-content shareability entirely.

## Severity rationale

Critical because static metadata on dynamic routes makes every instance of that route present identically in social feeds, eliminating the individual shareability of each piece of content.

## Remediation

Replace static `metadata` exports on dynamic routes with `generateMetadata` functions that fetch the specific item and build OG tags from its data:

```ts
// app/blog/[slug]/page.tsx
// WRONG:
export const metadata: Metadata = { openGraph: { title: 'My Blog' } }

// RIGHT:
export async function generateMetadata({ params }: { params: { slug: string } }): Promise<Metadata> {
  const post = await getPostBySlug(params.slug)
  if (!post) return { title: 'Not Found' }
  return {
    openGraph: {
      title: post.title,
      description: post.excerpt,
      images: [{ url: post.ogImageUrl ?? '/og-default.png', width: 1200, height: 630 }],
      url: `https://yoursite.com/blog/${params.slug}`,
    },
  }
}
```

Apply this pattern to every `[param]` route directory in `app/` that renders public-facing content.

## Detection

- **ID:** `dynamic-og-for-dynamic-content`
- **Severity:** `critical`
- **What to look for:** Identify all dynamic routes in the project — pages with parameterized paths like `[slug]`, `[id]`, `[username]`. For each dynamic route, check whether OG metadata is generated dynamically via `generateMetadata` (Next.js App Router), `getHead` (Nuxt), or equivalent framework mechanism. Static `metadata` exports on dynamic route pages are a failure pattern: they apply the same metadata to every instance of that route regardless of the actual content. Count all instances found and enumerate each.
- **Pass criteria:** Every dynamic route that renders public-facing content uses `generateMetadata` (or framework equivalent) to construct OG tags from the route's data. The function fetches or derives the title, description, and image from the specific item being rendered. At least 1 implementation must be confirmed.
- **Fail criteria:** Any dynamic route uses a static `metadata` export (not `generateMetadata`) for its OG tags, OR any dynamic route has no OG metadata at all. Do NOT pass if all dynamic routes share the same static og:title and og:description — each piece of content needs unique social previews.
- **Skip (N/A) when:** The project has no dynamic routes — all routes are static paths. Signal: no `[param]` folders in `app/` or `pages/`, no dynamic route patterns.
- **Cross-reference:** The `social-meta-per-content-type` check verifies that different content types generate distinct social meta, complementing this dynamic OG check.
- **Detail on fail:** `"app/blog/[slug]/page.tsx uses static metadata export — all blog posts will show the same OG title and description regardless of content"` or `"app/products/[id]/page.tsx has no OG metadata — each product page shows no social preview"`
- **Remediation:** Dynamic routes need dynamic metadata. Replace static `metadata` exports with `generateMetadata` functions that fetch the specific item's data:

  ```ts
  // app/blog/[slug]/page.tsx
  // WRONG:
  export const metadata: Metadata = { openGraph: { title: 'My Blog' } }

  // RIGHT:
  export async function generateMetadata({ params }: { params: { slug: string } }): Promise<Metadata> {
    const post = await getPostBySlug(params.slug)
    if (!post) return { title: 'Not Found' }
    return {
      openGraph: {
        title: post.title,
        description: post.excerpt,
        images: [{ url: post.ogImageUrl ?? '/og-default.png', width: 1200, height: 630 }],
        url: `https://yoursite.com/blog/${params.slug}`,
      },
    }
  }
  ```

  This is the most common and highest-impact social sharing gap in AI-built projects.

## External references

- external ogp.me — https://ogp.me/

Taxons: findability

HTML version: https://auditbuffet.com/patterns/ab-001820
