Загрузка…
Загрузка…
generateMetadata и page могут вызывать один и тот же getPost — React dedupe. Плюс typed sitemap.ts для динамической карты сайта.
// app/blog/[slug]/page.tsx
import type { Metadata } from 'next';
// Dynamic metadata — fetch is DEDUPED with the page body's fetch
export async function generateMetadata(
{ params }: { params: Promise }
): Promise {
const { slug } = await params;
const post = await getPost(slug); // same call the page makes → one request
return {
title: post.title, // → "post.title · Acme" via template
description: post.excerpt,
alternates: { canonical: `/blog/${slug}` }, // canonical URL — dedupe signal
openGraph: {
title: post.title,
images: [{ url: post.ogImage, width: 1200, height: 630 }],
type: 'article',
},
twitter: { card: 'summary_large_image' },
};
}
export default async function Page({ params }: { params: Promise }) {
const { slug } = await params;
const post = await getPost(slug); // deduped with the call above
return {post.body};
}// app/sitemap.ts — typed, dynamic sitemap generated at request/build time
import type { MetadataRoute } from 'next';
export default async function sitemap(): Promise {
const posts = await getAllPosts();
return posts.map((p) => ({
url: `
lastModified: p.updatedAt,
changeFrequency: 'weekly',
priority: 0.7,
}));
}Canonical, Open Graph и Twitter card задаются в одном объекте Metadata. Sitemap — отдельный convention file с типом MetadataRoute.Sitemap.
Паттерн: один data fetch на slug, metadata и page делят promise; sitemap генерируется из того же источника постов.
