fix(docs): align SEO metadata with page content

Co-authored-by: Junior Garcia <jrgarciadev@gmail.com>
This commit is contained in:
Cursor Agent
2026-08-13 21:28:43 +00:00
committed by Junior Garcia
parent 56f2278c25
commit 2e65a8b5a9
9 changed files with 55 additions and 39 deletions
@@ -26,6 +26,7 @@ import {PRContributors, fetchPRContributors} from "@/components/pr-contributors"
import StatusChip from "@/components/status-chip";
import {siteConfig} from "@/config/site";
import {getComponentCount, getExampleCount} from "@/demos";
import {getDocsSeoMetadata} from "@/lib/docs-seo";
import {getBreadcrumbJsonLd, getTechArticleJsonLd} from "@/lib/json-ld";
import {absoluteUrl, getLocalizedAlternates, localizedPath, stripLocale} from "@/lib/seo";
import {source} from "@/lib/source";
@@ -38,28 +39,6 @@ import {
// import { getGithubLastEdit } from "fumadocs-core/server";
const componentStatusIcons = ["preview", "new", "updated"];
const ENGLISH_SEO_OVERRIDES: Record<string, {description: string; title: string}> = {
"/docs/react/components": {
description:
"Browse accessible HeroUI React components for forms, overlays, navigation, data display, and more, built with React Aria and Tailwind CSS v4.",
title: "HeroUI React Components Accessible UI Library",
},
"/docs/react/components/button": {
description:
"Build accessible React buttons with HeroUI. Explore variants, sizes, loading and disabled states, icon buttons, events, and ButtonGroup patterns.",
title: "HeroUI Button Accessible React Button Component",
},
"/docs/react/components/select": {
description:
"Build accessible React select menus with HeroUI. Learn options, sections, validation, disabled states, controlled values, and customizable listboxes.",
title: "HeroUI Select Accessible React Select Component",
},
"/docs/react/getting-started": {
description:
"Get started with HeroUI v3, an accessible React UI library built on React Aria and Tailwind CSS v4. Learn installation, principles, and customization.",
title: "HeroUI v3 React UI Library Getting Started",
},
};
const getRawMDXContent = cache(async (pagePath: string): Promise<string> => {
try {
@@ -111,6 +90,7 @@ export default async function Page(props: {params: Promise<{lang: string; slug?:
const slugParts = params.slug ?? [];
const pagePath = `/docs/${slugParts.join("/")}`;
const pageUrl = absoluteUrl(localizedPath(params.lang, pagePath));
const seoMetadata = getDocsSeoMetadata(params.lang, pagePath);
const breadcrumbItems = [
{name: "Home", url: absoluteUrl(localizedPath(params.lang))},
@@ -132,8 +112,8 @@ export default async function Page(props: {params: Promise<{lang: string; slug?:
dangerouslySetInnerHTML={{
__html: JSON.stringify(
getTechArticleJsonLd({
description: page.data.description ?? "",
title: page.data.title,
description: seoMetadata?.description ?? page.data.description ?? "",
title: seoMetadata?.title ?? page.data.title,
url: pageUrl,
}),
),
@@ -219,7 +199,7 @@ export async function generateMetadata(props: {
const url = page.url;
const unlocalizedPath = stripLocale(url);
const alternates = getLocalizedAlternates({locale: params.lang, path: unlocalizedPath});
const seoOverride = params.lang === "en" ? ENGLISH_SEO_OVERRIDES[unlocalizedPath] : undefined;
const seoOverride = getDocsSeoMetadata(params.lang, unlocalizedPath);
const description = seoOverride?.description ?? page.data.description;
const title = seoOverride?.title ?? page.data.title;
+1 -1
View File
@@ -68,7 +68,7 @@ export default async function ThemeBuilderPage({params}: {params: Promise<{lang:
className="grid h-dvh grid-rows-[auto_1fr_auto] bg-background px-4 sm:overflow-hidden sm:px-6"
id={THEME_BUILDER_PAGE_ID}
>
<h1 className="sr-only">{dict.themes.metaTitle}</h1>
<h1 className="sr-only">{dict.themes.heading}</h1>
<BuilderHeader />
<ThemeBuilderContent />
<div className="mx-auto hidden items-center justify-between gap-4 py-6 max-[1200px]:flex-col sm:flex">
+6 -2
View File
@@ -8,6 +8,8 @@ import {notFound} from "next/navigation";
import {ImageResponse} from "next/og";
import {HeroUILogo} from "@/components/heroui-logo";
import {getDocsSeoMetadata} from "@/lib/docs-seo";
import {stripLocale} from "@/lib/seo";
import {source} from "@/lib/source";
interface GenerateProps {
@@ -93,8 +95,10 @@ export const GET = async (_req: Request, {params}: {params: Promise<{slug: strin
if (!page) notFound();
const seoMetadata = getDocsSeoMetadata(page.locale, stripLocale(page.url));
return generateOGImage({
description: page.data.description,
description: seoMetadata?.description ?? page.data.description,
fonts: [
{
data: interRegular,
@@ -108,7 +112,7 @@ export const GET = async (_req: Request, {params}: {params: Promise<{slug: strin
},
],
icon: <HeroUILogo size={58} />,
title: page.data.title,
title: seoMetadata?.title ?? page.data.title,
});
};
+2 -1
View File
@@ -117,8 +117,9 @@
"androidComingSoon": "Android · 即将推出"
},
"themes": {
"heading": "主题编辑器",
"metaTitle": "HeroUI 主题编辑器 自定义 React UI",
"metaDescription": "通过可视化控件自定义 HeroUI React 主题,调整颜色、字体与圆角,实时预览组件并复制可用于生产环境的 Tailwind CSS v4 变量。",
"metaDescription": "通过可视化控件自定义 HeroUI React 主题,调整颜色、字体与圆角,实时预览组件并复制可用于生产环境的 HeroUI CSS 变量。",
"theme": "主题",
"custom": "自定义",
"designTheme": "设计主题",
+2 -1
View File
@@ -117,8 +117,9 @@
"androidComingSoon": "Android · Coming soon"
},
"themes": {
"heading": "Theme Builder",
"metaTitle": "HeroUI Theme Builder Customize React UI",
"metaDescription": "Customize HeroUI React themes visually. Tune colors, typography, and radius, preview components live, and copy production-ready Tailwind CSS v4 variables.",
"metaDescription": "Customize HeroUI React themes visually. Tune colors, typography, and radius, preview components live, and copy production-ready HeroUI CSS variables.",
"theme": "Theme",
"custom": "Custom",
"designTheme": "Design theme",
+34
View File
@@ -0,0 +1,34 @@
export interface DocsSeoMetadata {
description: string;
title: string;
}
const ENGLISH_DOCS_SEO_METADATA: Readonly<Record<string, DocsSeoMetadata>> = {
"/docs/react/components": {
description:
"Browse accessible HeroUI React components for forms, overlays, navigation, data display, and more, built with React Aria and Tailwind CSS v4.",
title: "HeroUI React Components Accessible UI Library",
},
"/docs/react/components/button": {
description:
"Build accessible React buttons with HeroUI. Explore variants, sizes, icon-only states, custom styles, ripple effects, render props, and BEM classes.",
title: "HeroUI Button Accessible React Button Component",
},
"/docs/react/components/select": {
description:
"Build accessible React select inputs with HeroUI. Explore single and multiple selection, async loading, sections, disabled items, and controlled values.",
title: "HeroUI Select Accessible React Select Component",
},
"/docs/react/getting-started": {
description:
"Meet HeroUI v3, an accessible React UI library built on React Aria and Tailwind CSS v4. Explore its design approach, ecosystem, and common questions.",
title: "Introduction to HeroUI v3 React UI Library",
},
};
export function getDocsSeoMetadata(
locale: string | undefined,
unlocalizedPath: string,
): DocsSeoMetadata | undefined {
return locale === "en" ? ENGLISH_DOCS_SEO_METADATA[unlocalizedPath] : undefined;
}
+1 -1
View File
@@ -65,7 +65,7 @@ node scripts/get_hooks_migration_guide.mjs
### Direct URLs
Migration docs (preview): `https://heroui-git-docs-migration-heroui.vercel.app/docs/react/migration/{filename}`
Migration docs (preview): use a concrete guide URL from the examples below, and never fetch a URL that still contains a placeholder.
Examples:
+2 -4
View File
@@ -76,8 +76,7 @@ node scripts/get_docs.mjs /docs/native/getting-started/theming
### Direct MDX URLs
Use a concrete kebab-case component slug in every MDX request. Never request an unresolved
template URL.
Component docs: fetch `.mdx` with a concrete kebab-case slug. Run `node scripts/list_components.mjs` when the slug is unknown, and never fetch a URL that still contains a placeholder.
Examples:
@@ -85,8 +84,7 @@ Examples:
- Dialog: `https://heroui.com/docs/native/components/dialog.mdx`
- TextField: `https://heroui.com/docs/native/components/text-field.mdx`
For getting-started guides, use a concrete topic URL such as
`https://heroui.com/docs/native/getting-started/quick-start.mdx`.
Getting started guides: use a concrete topic URL such as `https://heroui.com/docs/native/getting-started/quick-start.mdx`.
**Important:** Always fetch component docs before implementing. The MDX docs include complete examples, props, anatomy, and API references.
+2 -4
View File
@@ -98,8 +98,7 @@ node scripts/get_docs.mjs /docs/react/getting-started/theming
### Direct MDX URLs
Use a concrete kebab-case component slug in every MDX request. Never request an unresolved
template URL.
Component docs: fetch `.mdx` with a concrete kebab-case slug. Run `node scripts/list_components.mjs` when the slug is unknown, and never fetch a URL that still contains a placeholder.
Examples:
@@ -107,8 +106,7 @@ Examples:
- Modal: `https://heroui.com/docs/react/components/modal.mdx`
- Form: `https://heroui.com/docs/react/components/form.mdx`
For getting-started guides, use a concrete topic URL such as
`https://heroui.com/docs/react/getting-started/quick-start.mdx`.
Getting started guides: use a concrete topic URL such as `https://heroui.com/docs/react/getting-started/quick-start.mdx`.
**Important:** Always fetch component docs before implementing. The MDX docs include complete examples, props, anatomy, and API references.