Claude Fable 5.1 & GPT-6 Astra packages are live

Metadata

Free · MIT

Page metadata — titles, descriptions, Open Graph, icons and canonical tags generated on the server, per route, without duplication or leaks.

207 lines8.3 KB Mistral Frontend
Target models
Mistral Medium 3.5Mistral Large 3Mistral Small 4Mistral FamilyFuture Mistral Models
Name
metadata
Category
Frontend
Description
Page metadata — titles, descriptions, Open Graph, icons and canonical tags generated on the server, per route, without duplication or leaks.
License
MIT
Author
Agent.md maintainers
Last verified
2026-08-23
Reviewed by
unreviewed

#How to apply this file

Each section opens with one imperative line; apply every rule in the section it introduces. Do not summarise or skip a section.


#Purpose

Rules for the <head>: titles, descriptions, social cards, icons, canonical links and robots directives.

Two constraints shape everything: metadata must be in the server response (crawlers and social preview bots do not run your JavaScript), and it must be unique per page (duplicated metadata is the most common technical SEO fault).

Search strategy is Frontend/seo.


#Generate it on the server, per route

[INST] Apply every rule in this section: Generate it on the server, per route. [/INST]

tsx
// Next.js App Router — resolved during the server render
export async function generateMetadata({ params }): Promise<Metadata> {
  const product = await getProduct(params.id);
  if (!product) return { title: "Not found", robots: { index: false } };

  return {
    title: product.name,                                  // template applies the suffix
    description: truncate(product.summary, 155),
    alternates: { canonical: `/products/${product.slug}` },
    openGraph: {
      title: product.name,
      images: [{ url: `/og/products/${product.slug}.png`, width: 1200, height: 630 }],
      type: "website",
    },
    twitter: { card: "summary_large_image" },
  };
}

A root layout defines the defaults and the title template:

ts
export const metadata: Metadata = {
  metadataBase: new URL("https://example.com"),     // makes relative URLs absolute
  title: { default: "Acme", template: "%s — Acme" },
  description: "…",
};

metadataBase matters more than it looks: without it, Open Graph image paths stay relative, and every social preview silently fails. Preview bots do not resolve relative URLs.

Never set metadata in useEffect or with a client-side helper. It runs after the crawler has already taken the response.


#Write it for the result, not for the page

[INST] Apply every rule in this section: Write it for the result, not for the page. [/INST]

TagLimitRule
<title>~60 charactersMost specific first: Widget — Acme, not Acme — Widget
description~155 charactersA sentence a human would click; not keyword filler
og:title~60May differ from <title> — social context is different
og:image1200×630, < 8 MBAbsolute URL, PNG or JPEG
og:description~200
twitter:cardsummary_large_image for anything with an image

Every page needs its own title and description. A catalogue where 400 products share one description is a catalogue where 399 pages do not rank.

Truncate generated descriptions on a word boundary, and strip markup — a description containing raw HTML is rendered literally in results.

Dynamic Open Graph images (@vercel/og, Satori) generate a per-page card from the same data the page shows. Cache them immutably by content hash; regenerating an image on every crawl is wasted work.


#Robots directives

[INST] Apply every rule in this section: Robots directives. [/INST]

ts
robots: { index: false, follow: true }        // → <meta name="robots" content="noindex,follow">

Set noindex deliberately on: staging environments, search-result pages, filtered and sorted views, thank-you and confirmation pages, user-specific pages, and anything behind authentication.

Guard against shipping noindex to production. A staging default that leaks into a production build removes a site from search results, and recovery takes weeks. Make it explicit per environment and assert it in a smoke test:

bash
curl -sI https://example.com/ | grep -i "x-robots-tag" && exit 1   # must be absent

robots.txt blocks crawling, not indexing — and a page blocked there can never have its noindex read. To remove a page from the index, allow the crawl and use the meta tag.


#Icons, manifest and language

[INST] Apply every rule in this section: Icons, manifest and language. [/INST]

html
<link rel="icon" href="/favicon.ico" sizes="32x32" />
<link rel="icon" href="/icon.svg" type="image/svg+xml" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />   
<link rel="manifest" href="/manifest.webmanifest" />
<meta name="theme-color" content="#0b0b0c" media="(prefers-color-scheme: dark)" />
  • <html lang="en"> on every page, matching the actual content language. Screen readers choose pronunciation from it. → Testing/accessibility
  • <meta name="viewport" content="width=device-width, initial-scale=1"> — required for mobile rendering, and its absence is a mobile-usability failure.
  • dir="rtl" where the content requires it.

#Do not leak through metadata

[INST] Apply every rule in this section: Do not leak through metadata. [/INST]

  • Metadata is public. Never put an internal identifier, an email address, a draft title, or anything user-specific into a tag on a public page.
  • Pages behind authentication should be noindex and should not generate social cards containing the user's data.
  • A 404 must return a real 404 status and noindex — a soft 404 returning 200 with generic metadata gets indexed as a real page. → Frontend/routing
  • Do not include version numbers, framework fingerprints or build paths in <meta> tags.

#Anti-patterns

[INST] Apply every rule in this section: Anti-patterns. [/INST]

Anti-patternWhy it failsFix
Metadata set client-sideCrawlers and preview bots never see itServer-generated
One title for the whole applicationEvery page competes with itselfPer-route metadata
Duplicate descriptions across a cataloguePages do not differentiateGenerate per record
No metadataBaseRelative OG images break every previewSet it once at the root
Relative og:imageSocial previews silently failAbsolute URL
Titles over 60 charactersTruncated in resultsKeep them short, specific first
Keyword-stuffed descriptionsIgnored or penalised; poor click-throughWrite for a human
Raw HTML in a descriptionRendered literallyStrip markup
noindex leaking to productionSite removed from search for weeksEnvironment-explicit; smoke test
robots.txt used to deindexnoindex can never be readAllow crawl, use the meta tag
Missing <html lang>Wrong screen-reader pronunciationSet it per locale
Missing viewport metaMobile rendering breaksAdd it
Soft 404 with generic metadataEmpty pages indexedReal 404 plus noindex
User data in social cardsPublic disclosurenoindex, no personalised cards
OG images regenerated per requestWasted computeCache immutably

#Checklist

  • Verify: All metadata is produced in the server response
  • Verify: A root default and title template are defined once
  • Verify: metadataBase is set so relative URLs resolve absolutely
  • Verify: Every route generates its own title and description
  • Verify: Titles are within ~60 characters, most specific part first
  • Verify: Descriptions are within ~155 characters, human-readable, markup-free
  • Verify: Open Graph and Twitter tags are present with absolute image URLs
  • Verify: Social images are 1200×630 and cached immutably
  • Verify: noindex is applied deliberately and never leaks to production
  • Verify: A smoke test asserts production pages are indexable
  • Verify: Canonical URLs are set per page
  • Verify: Icons, manifest and theme-color are configured
  • Verify: <html lang> and the viewport meta tag are present on every page
  • Verify: 404 responses carry a real status code and noindex
  • Verify: No internal, user-specific or fingerprinting data appears in metadata