P PasteCode
Failure

How to Fix AI Ignoring SEO Metadata

AI agents scaffold Next.js and Astro pages without title tags, Open Graph metadata, or canonical URLs, shipping pages that are invisible to search engines and social crawlers.

CursorClaude CodeCodexWindsurf Next.jsAstroTypeScript
.md .json Updated Jun 8, 2026

The agent generates page components with no Metadata export, no title tag, and no Open Graph tags, producing pages that rank poorly and share badly on social media.

The symptom

A Next.js App Router page is scaffolded without any metadata export, leaving the tab title blank and the head empty beyond Next.js defaults.

// app/blog/[slug]/page.tsx — WRONG
export default async function BlogPost({ params }: { params: { slug: string } }) {
const post = await getPost(params.slug);
return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.html }} />
</article>
);
// No <title>, no meta description, no og:image, no canonical URL
}

Google sees: Untitled document. Twitter/LinkedIn show a blank card.

Why it happens

The agent focuses on the visible UI — data fetching and rendering — and treats head metadata as an afterthought. SEO is invisible during local development and doesn’t produce a runtime error, so the agent never gets feedback that it’s missing.

How to spot it

  • No export const metadata or export async function generateMetadata in App Router page files.
  • No Astro.props or frontmatter-driven title / description in Astro layouts.
  • Lighthouse SEO score below 90 on a new page.
  • curl -s http://localhost:3000/blog/my-post | grep "<title>" returns nothing or a generic site title.
  • og:image missing from the head (social preview shows a blank card).

How to fix it

Export generateMetadata for dynamic pages and add all required tags.

// app/blog/[slug]/page.tsx — CORRECT
import type { Metadata } from "next";
export async function generateMetadata({
params,
}: {
params: { slug: string };
}): Promise<Metadata> {
const post = await getPost(params.slug);
return {
title: post.title,
description: post.excerpt,
alternates: { canonical: `https://example.com/blog/${params.slug}` },
openGraph: {
title: post.title,
description: post.excerpt,
url: `https://example.com/blog/${params.slug}`,
images: [{ url: post.ogImage, width: 1200, height: 630, alt: post.title }],
type: "article",
publishedTime: post.publishedAt,
},
twitter: {
card: "summary_large_image",
title: post.title,
description: post.excerpt,
images: [post.ogImage],
},
};
}
export default async function BlogPost({ params }: { params: { slug: string } }) {
const post = await getPost(params.slug);
return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.html }} />
</article>
);
}

For Astro, pass metadata through the layout:

src/pages/blog/[slug].astro
---
const { post } = Astro.props;
---
<Layout
title={post.title}
description={post.excerpt}
ogImage={post.ogImage}
canonical={`https://example.com/blog/${post.slug}`}
>
<article>
<h1>{post.title}</h1>
<post.Content />
</article>
</Layout>
[ ] Every page exports generateMetadata (Next.js) or passes title+description to layout (Astro)
[ ] title is unique per page — not just the site name
[ ] description is 120-160 characters and describes the page content
[ ] og:title, og:description, og:image (1200x630), og:url are all set
[ ] twitter:card is "summary_large_image" for pages with images
[ ] canonical URL is set to the preferred URL (no trailing slash ambiguity)
[ ] robots meta is not accidentally set to "noindex"
[ ] Verify with: curl -s <url> | grep -E "<title>|og:title|description"

Fix Prompt

Fix Prompt
This page component is missing all SEO metadata. Add an async generateMetadata
export (Next.js App Router) that returns title, description, canonical URL, full
openGraph object (title, description, url, images with 1200x630 dimensions, type),
and twitter card metadata. Derive values from the page's data fetch — do not use
placeholder strings. Also confirm that no parent layout accidentally sets
robots: noindex.

Test

Terminal window
# Check that <title> and og:title appear in the server-rendered HTML
curl -s http://localhost:3000/blog/my-post \
| grep -E '<title>|og:title|og:description|og:image|canonical' \
| grep -v "node_modules" \
&& echo "Metadata present" || echo "FAIL: metadata missing"