Technical AEO
Next.js SEO: Connect Article Data, Metadata and Routes
This guide is part of the King of AEO learning library.
The short answer
Next.js SEO starts with a coherent content model: the same published record should drive the visible article, title, description, canonical URL and discovery files. Keep core content available in server output and handle unknown routes intentionally. Test the deployed application because metadata streaming, caching and hosting behaviour can differ from assumptions made during development.
In this guide
Build from one public article recordGenerate metadata deliberatelyKeep the article on the server side of the boundaryTreat missing routes as a real response caseCheck metadata streaming without cargo-cult fixesVerify the deployed content systemWire the article route to its actual recordSourcesBuild from one public article record
Give each article a stable slug, visible title, concise search title, description, publication state and persistent dates. Store these values with the content rather than scattering them across route components, sitemap code and metadata utilities. A shared public article lookup can enforce publication rules consistently. When an editor withdraws a draft or changes an address, every consumer of that lookup should agree. This prevents a page from disappearing visually while its metadata or sitemap continues to advertise it.
An illustrative library might store content in local records or a CMS. Either approach can work if the application has a clear boundary between preview content and public content. A preview session may expose an unpublished guide to its editor; the unauthenticated route must not do so accidentally. Keep the public URL builder separate from the current request hostname so production metadata does not use temporary deployment addresses. The reasoning behind a preferred address is explained in canonical URLs.
Generate metadata deliberately
The App Router's generateMetadata reference documents both metadata generation and how values from route segments combine. Use an exported metadata object for fixed page information, or generateMetadata when values depend on the requested article. Read the article once through a consistent data access layer and derive the relevant fields. Do not make metadata depend on a separate editorial record that can drift from the title and answer readers actually receive on the page.
Pay attention to nested objects. Metadata inheritance does not mean every nested field is recursively combined in the way an application developer might expect. If a route defines its own social metadata, explicitly include the shared fields it still needs. A common symptom is an article with the right title but no intended image or description. Inspect the final output for several routes, including a long title and a missing optional image. Good defaults should be useful, while article specific values should remain accurate.
Article record: Published content and persistent facts
Public lookup: Resolve slug and publication state
Article HTML: Readable server output
Metadata: Title and canonical from same record
Not found: Unknown record follows error handling
One article record drives public output. Unknown records need explicit routing and deployed response checks.
Keep the article on the server side of the boundary
Use server rendered content for the main explanation where practical, with small client components for genuinely interactive features. Marking an entire article layout as client code simply because its copy button needs state expands the browser's responsibilities unnecessarily. A narrower boundary is easier to reason about. The article can contain ordinary headings, paragraphs and links, while the button receives only the text or identifier it needs. This also reduces the chance that an interaction failure hides the guide.
Do not assume that a particular component label alone proves what the server sends. Inspect the resulting response and rendered page. Data fetching, suspense boundaries and dynamic requirements can affect what arrives initially. When content is cached, decide what event refreshes it and how quickly a published correction becomes visible. If an editor fixes an inaccurate paragraph, the public content, metadata and any derived structured data should converge on the same version. General rendering trade-offs are covered in JavaScript SEO.
Treat missing routes as a real response case
An unknown slug should follow an intentional not-found path, with helpful navigation and no invented article content. The Next.js not-found documentation includes a material nuance: non-streamed not-found responses return 404, while streamed responses can return 200. Therefore calling notFound is not sufficient evidence that every deployed missing route has the same HTTP status. Understand when your route commits its response and test the actual behaviour in the version and hosting setup you use.
For a stable article library, resolve whether the requested article exists as early as the architecture permits. Avoid sending a successful article shell and discovering much later that no record exists. Test both an entirely unmatched address and an unknown slug within a valid dynamic route, because they may take different paths. A helpful visual error page still needs appropriate machine readable handling. The distinctions between missing, moved and temporarily unavailable resources are explained in HTTP status codes.
Check metadata streaming without cargo-cult fixes
Current Next.js documentation describes streaming metadata and different treatment for HTML limited bots. This means a quick search through only the beginning of a response may not tell the full story. Inspect the complete response, final DOM and the framework's documented behaviour for the relevant client. Avoid disabling a feature merely because an older tutorial expects every tag in one location. Equally, do not assume that all consumers interpret the page exactly as a fully capable browser does.
Choose any configuration change in response to a demonstrated compatibility problem. If a sharing service displays stale information, separate fetch caching, image accessibility and metadata delivery before altering global rendering behaviour. A global override may affect response timing across the site. Keep a small reproducible test route or request that demonstrates the failure and confirms the correction. This makes future framework upgrades safer because the team can verify the outcome it needs instead of preserving unexplained configuration indefinitely.
Verify the deployed content system
A production check should cover the home page, a category, representative articles and invalid routes. Request them directly as fresh visitors, rather than navigating only through a running development session. Confirm article text, heading hierarchy, metadata, canonical addresses and internal destinations. Check the generated XML sitemap against published records. If the deployment uses a static export, inspect how the host serves nested routes and errors. If it uses a runtime, inspect its caching and data availability during ordinary requests.
Make dates persistent across builds. A deployment that changes a stylesheet should not silently republish the entire library with new article dates. Include this expectation in technical release checks, along with a sample of source links and any structured data derived from the content. Next.js supplies mechanisms, but the application still owns the editorial meaning of its fields. A reliable implementation is one where a reader, a raw request and the discovery metadata all describe the same published article.
Separate a missing optional field from a missing article. A guide without a custom social image can use a truthful default image, while a nonexistent slug should never fall back to an unrelated guide. Likewise, a temporarily unavailable CMS should not silently publish empty paragraphs under a successful title. Decide these fallback behaviours in the data layer and make them visible in operational logs. The distinction helps the application preserve useful metadata without disguising a content failure as a complete publication.
When testing an update, check a direct request and a client navigation to the same article. They can expose different stale states, particularly when the browser retains route data after an editor publishes a correction. A hard refresh that looks correct does not prove a visitor already browsing the library receives the update under your chosen policy. Define the intended freshness window, verify the relevant cache invalidation and explain that window internally. Avoid repeated global cache changes when one content lookup is responsible for the inconsistency.
Wire the article route to its actual record
The following App Router excerpt shows the boundary between an existing content lookup and generated metadata. It assumes getArticle reads the published article records and returns undefined for an unknown slug. The same lookup should be used by the page component when rendering the heading and body. Keep the root metadataBase configured to the production origin if other metadata fields use relative URLs.
Export generateStaticParams for the known slugs when building a stable library, and use dynamicParams = false when other article slugs must not be generated on demand. Those route exports are separate from this metadata function. A production request to an unknown slug should be part of your release check. Do not use a successful metadata result as evidence that the article body, status code or sitemap is also correct.
For the complete structured record accompanying this metadata, follow the Article schema implementation guide. It connects the published work, responsible organisation, image and persistent dates.
import type { Metadata } from 'next';
import { notFound } from 'next/navigation';
import { getArticle } from '@/lib/content';
import { siteUrl } from '@/lib/site';
export async function generateMetadata({ params }: {
params: Promise<{ slug: string }>
}): Promise<Metadata> {
const article = getArticle((await params).slug);
if (!article) notFound();
return {
title: article.metaTitle,
description: article.description,
alternates: {
canonical: `${siteUrl}/learn/${article.slug}`
},
openGraph: {
type: 'article',
publishedTime: article.published,
modifiedTime: article.modified
}
};
}Sources and further reading
- Next.js generateMetadata referenceDocuments metadata generation, shallow merging and streaming metadata behaviour.
- Next.js not-found conventionNot-found responses are 404 when non-streamed and can be 200 when streamed.