Skip to content

Commit f123f5b

Browse files
ericyangpanclaude
andcommitted
refactor(pages): adopt centralized metadata generators and LocalePageProps
Migrate all page components to use: - LocalePageProps type for consistent locale param handling - generateStaticPageMetadata() for home, manifesto, search, landscape, stack, and collections pages - Centralized metadata generators with proper robots configuration - Consistent basePath handling (empty string for root instead of '/') Affects 16 page files across the application. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 9334cf7 commit f123f5b

File tree

16 files changed

+158
-316
lines changed

16 files changed

+158
-316
lines changed

src/app/[locale]/ai-coding-landscape/page.tsx

Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,41 +4,26 @@ import Header from '@/components/Header'
44
import { BackToNavigation } from '@/components/navigation/BackToNavigation'
55
import PageHeader from '@/components/PageHeader'
66
import VendorMatrix from '@/components/product/VendorMatrix'
7+
import type { Locale } from '@/i18n/config'
78
import { buildVendorMatrix } from '@/lib/landscape-data'
8-
import { buildCanonicalUrl, buildOpenGraph, buildTitle, buildTwitterCard } from '@/lib/metadata'
9+
import { buildTitle, generateStaticPageMetadata } from '@/lib/metadata'
910

1011
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) {
1112
const { locale } = await params
1213
const tNav = await getTranslations({ locale, namespace: 'components.header' })
1314

14-
const canonicalPath = locale === 'en' ? '/ai-coding-landscape' : `/${locale}/ai-coding-landscape`
1515
const title = buildTitle({ title: tNav('aiCodingLandscape') })
1616
const description = tNav('aiCodingLandscapeDesc')
1717

18-
return {
18+
return generateStaticPageMetadata({
19+
locale: locale as Locale,
20+
basePath: 'ai-coding-landscape',
1921
title,
2022
description,
2123
keywords:
2224
'AI coding ecosystem, AI development landscape, AI tools, coding tools visualization, vendor comparison, product matrix',
23-
alternates: {
24-
canonical: canonicalPath,
25-
languages: {
26-
en: '/ai-coding-landscape',
27-
'zh-Hans': '/zh-Hans/ai-coding-landscape',
28-
},
29-
},
30-
openGraph: buildOpenGraph({
31-
title: tNav('aiCodingLandscape'),
32-
description,
33-
url: buildCanonicalUrl({ path: canonicalPath, locale }),
34-
locale,
35-
type: 'website',
36-
}),
37-
twitter: buildTwitterCard({
38-
title: tNav('aiCodingLandscape'),
39-
description,
40-
}),
41-
}
25+
ogType: 'website',
26+
})
4227
}
4328

4429
type Props = {

src/app/[locale]/ai-coding-stack/page.tsx

Lines changed: 10 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,48 +2,30 @@ import { getTranslations } from 'next-intl/server'
22
import Footer from '@/components/Footer'
33
import Header from '@/components/Header'
44
import PageHeader from '@/components/PageHeader'
5+
import type { Locale } from '@/i18n/config'
56
import { Link } from '@/i18n/navigation'
6-
import { buildCanonicalUrl, buildOpenGraph, buildTitle, buildTwitterCard } from '@/lib/metadata'
7+
import { buildTitle, generateStaticPageMetadata } from '@/lib/metadata'
8+
import type { LocalePageProps } from '@/types/locale'
79

8-
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) {
10+
export async function generateMetadata({ params }: LocalePageProps) {
911
const { locale } = await params
1012
const t = await getTranslations({ locale, namespace: 'pages.overview' })
1113

12-
const canonicalPath = locale === 'en' ? '/ai-coding-stack' : `/${locale}/ai-coding-stack`
1314
const title = buildTitle({ title: t('title') })
1415
const description = t('subtitle')
1516

16-
return {
17+
return generateStaticPageMetadata({
18+
locale: locale as Locale,
19+
basePath: 'ai-coding-stack',
1720
title,
1821
description,
1922
keywords:
2023
'AI Coding Stack, AI development tools, AI IDE, AI CLI, LLM models, AI coding ecosystem',
21-
alternates: {
22-
canonical: canonicalPath,
23-
languages: {
24-
en: '/ai-coding-stack',
25-
'zh-Hans': '/zh-Hans/ai-coding-stack',
26-
},
27-
},
28-
openGraph: buildOpenGraph({
29-
title: t('title'),
30-
description,
31-
url: buildCanonicalUrl({ path: canonicalPath, locale }),
32-
locale,
33-
type: 'website',
34-
}),
35-
twitter: buildTwitterCard({
36-
title: t('title'),
37-
description,
38-
}),
39-
}
24+
ogType: 'website',
25+
})
4026
}
4127

42-
type Props = {
43-
params: Promise<{ locale: string }>
44-
}
45-
46-
export default async function AICodingStackPage({ params }: Props) {
28+
export default async function AICodingStackPage({ params }: LocalePageProps) {
4729
const { locale } = await params
4830
const t = await getTranslations({ locale, namespace: 'pages.overview' })
4931

src/app/[locale]/articles/page.tsx

Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,41 +2,26 @@ import { getTranslations } from 'next-intl/server'
22
import Footer from '@/components/Footer'
33
import Header from '@/components/Header'
44
import PageHeader from '@/components/PageHeader'
5+
import type { Locale } from '@/i18n/config'
56
import { Link } from '@/i18n/navigation'
67
import { getArticles } from '@/lib/generated/articles'
7-
import { buildCanonicalUrl, buildOpenGraph, buildTitle, buildTwitterCard } from '@/lib/metadata'
8+
import { buildTitle, generateStaticPageMetadata } from '@/lib/metadata'
89

910
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) {
1011
const { locale } = await params
1112
const t = await getTranslations({ locale, namespace: 'pages.articles' })
1213

13-
const canonicalPath = locale === 'en' ? '/articles' : `/${locale}/articles`
1414
const title = buildTitle({ title: `${t('title')} - AI Coding Insights & Tutorials` })
1515
const description = t('subtitle')
1616

17-
return {
17+
return generateStaticPageMetadata({
18+
locale: locale as Locale,
19+
basePath: 'articles',
1820
title,
1921
description,
2022
keywords: t('keywords'),
21-
alternates: {
22-
canonical: canonicalPath,
23-
languages: {
24-
en: '/articles',
25-
'zh-Hans': '/zh-Hans/articles',
26-
},
27-
},
28-
openGraph: buildOpenGraph({
29-
title: `${t('title')} - AI Coding Insights & Tutorials`,
30-
description,
31-
url: buildCanonicalUrl({ path: canonicalPath, locale }),
32-
locale,
33-
type: 'website',
34-
}),
35-
twitter: buildTwitterCard({
36-
title: `${t('title')} - AI Coding Insights & Tutorials`,
37-
description,
38-
}),
39-
}
23+
ogType: 'website',
24+
})
4025
}
4126

4227
type Props = {

src/app/[locale]/clis/[slug]/page.tsx

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import { getGithubStars } from '@/lib/generated/github-stars'
1515
import { translateLicenseText } from '@/lib/license'
1616
import { generateSoftwareDetailMetadata } from '@/lib/metadata'
1717
import { generateSoftwareDetailSchema } from '@/lib/metadata/schemas'
18-
import { transformCommunityUrls, transformResourceUrls } from '@/lib/product-utils'
1918

2019
export const revalidate = 3600
2120

@@ -71,13 +70,11 @@ export default async function CLIPage({
7170
const t = await getTranslations({ locale, namespace: 'pages.cliDetail' })
7271
const tGlobal = await getTranslations({ locale })
7372

74-
// Transform URLs
73+
// Transform URLs for component props
7574
const websiteUrl = cli.websiteUrl || cli.resourceUrls?.download || undefined
7675
const docsUrl = cli.docsUrl || undefined
7776
const downloadUrl = cli.resourceUrls?.download || undefined
78-
79-
const resourceUrls = transformResourceUrls(cli.resourceUrls)
80-
const communityUrls = transformCommunityUrls(cli.communityUrls)
77+
const pricingUrl = cli.resourceUrls?.pricing ?? undefined
8178

8279
// Generate JSON-LD schema
8380
const schema = await generateSoftwareDetailSchema({
@@ -138,9 +135,9 @@ export default async function CLIPage({
138135

139136
{relatedProducts.length > 0 && <RelatedProducts products={relatedProducts} />}
140137

141-
<ProductPricing pricing={cli.pricing} pricingUrl={resourceUrls.pricing} />
138+
<ProductPricing pricing={cli.pricing} pricingUrl={pricingUrl} />
142139

143-
<ProductLinks resourceUrls={resourceUrls} communityUrls={communityUrls} />
140+
<ProductLinks resourceUrls={cli.resourceUrls} communityUrls={cli.communityUrls} />
144141

145142
<ProductCommands install={cli.installCommand} launch={cli.launchCommand} />
146143

src/app/[locale]/curated-collections/page.tsx

Lines changed: 10 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,48 +4,30 @@ import Header from '@/components/Header'
44
import PageHeader from '@/components/PageHeader'
55
import CollectionSection from '@/components/product/CollectionSection'
66
import CollectionScrollbar from '@/components/sidebar/CollectionScrollbar'
7+
import type { Locale } from '@/i18n/config'
78
import { getCollectionSectionIds, getCollections } from '@/lib/collections'
8-
import { buildCanonicalUrl, buildOpenGraph, buildTitle, buildTwitterCard } from '@/lib/metadata'
9+
import { buildTitle, generateStaticPageMetadata } from '@/lib/metadata'
10+
import type { LocalePageProps } from '@/types/locale'
911

10-
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) {
12+
export async function generateMetadata({ params }: LocalePageProps) {
1113
const { locale } = await params
1214
const t = await getTranslations({ locale, namespace: 'pages.curatedCollections' })
1315

14-
const canonicalPath = locale === 'en' ? '/curated-collections' : `/${locale}/curated-collections`
1516
const title = buildTitle({ title: `${t('title')} - AI Coding Specs, Protocols & Tools` })
1617
const description = t('subtitle')
1718

18-
return {
19+
return generateStaticPageMetadata({
20+
locale: locale as Locale,
21+
basePath: 'curated-collections',
1922
title,
2023
description,
2124
keywords:
2225
'AI coding resources, MCP protocol, Agent2Agent, development standards, AI coding articles, semantic versioning, conventional commits',
23-
alternates: {
24-
canonical: canonicalPath,
25-
languages: {
26-
en: '/curated-collections',
27-
'zh-Hans': '/zh-Hans/curated-collections',
28-
},
29-
},
30-
openGraph: buildOpenGraph({
31-
title: `${t('title')} - AI Coding Specs, Protocols & Tools`,
32-
description,
33-
url: buildCanonicalUrl({ path: canonicalPath, locale }),
34-
locale,
35-
type: 'website',
36-
}),
37-
twitter: buildTwitterCard({
38-
title: `${t('title')} - AI Coding Specs, Protocols & Tools`,
39-
description,
40-
}),
41-
}
26+
ogType: 'website',
27+
})
4228
}
4329

44-
type Props = {
45-
params: Promise<{ locale: string }>
46-
}
47-
48-
export default async function CuratedCollectionsPage({ params }: Props) {
30+
export default async function CuratedCollectionsPage({ params }: LocalePageProps) {
4931
const { locale } = await params
5032
const t = await getTranslations({ locale, namespace: 'pages.curatedCollections' })
5133
const collections = getCollections(locale)

src/app/[locale]/docs/page.tsx

Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import { getTranslations } from 'next-intl/server'
22
import Footer from '@/components/Footer'
33
import Header from '@/components/Header'
44
import DocsSidebar from '@/components/sidebar/DocsSidebar'
5+
import type { Locale } from '@/i18n/config'
56
import { getDocComponent, getDocSections } from '@/lib/generated/docs'
6-
import { buildCanonicalUrl, buildOpenGraph, buildTitle, buildTwitterCard } from '@/lib/metadata'
7+
import { buildTitle, generateStaticPageMetadata } from '@/lib/metadata'
78

89
type Props = {
910
params: Promise<{ locale: string }>
@@ -13,33 +14,17 @@ export async function generateMetadata({ params }: { params: Promise<{ locale: s
1314
const { locale } = await params
1415
const t = await getTranslations({ locale, namespace: 'pages.docs' })
1516

16-
const canonicalPath = locale === 'en' ? '/docs' : `/${locale}/docs`
1717
const title = buildTitle({ title: t('title') })
1818
const description = t('subtitle')
1919

20-
return {
20+
return generateStaticPageMetadata({
21+
locale: locale as Locale,
22+
basePath: 'docs',
2123
title,
2224
description,
2325
keywords: t('keywords'),
24-
alternates: {
25-
canonical: canonicalPath,
26-
languages: {
27-
en: '/docs',
28-
'zh-Hans': '/zh-Hans/docs',
29-
},
30-
},
31-
openGraph: buildOpenGraph({
32-
title: t('title'),
33-
description,
34-
url: buildCanonicalUrl({ path: canonicalPath, locale }),
35-
locale,
36-
type: 'website',
37-
}),
38-
twitter: buildTwitterCard({
39-
title: t('title'),
40-
description,
41-
}),
42-
}
26+
ogType: 'website',
27+
})
4328
}
4429

4530
export default async function DocsPage({ params }: Props) {

src/app/[locale]/extensions/[slug]/page.tsx

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import { getGithubStars } from '@/lib/generated/github-stars'
1515
import { translateLicenseText } from '@/lib/license'
1616
import { generateSoftwareDetailMetadata } from '@/lib/metadata'
1717
import { generateSoftwareDetailSchema } from '@/lib/metadata/schemas'
18-
import { transformCommunityUrls, transformResourceUrls } from '@/lib/product-utils'
1918

2019
export const revalidate = 3600
2120

@@ -76,13 +75,11 @@ export default async function ExtensionPage({
7675
const t = await getTranslations({ locale, namespace: 'pages.extensionDetail' })
7776
const tGlobal = await getTranslations({ locale })
7877

79-
// Transform URLs
78+
// Transform URLs for component props
8079
const websiteUrl = extension.websiteUrl || extension.resourceUrls?.download || undefined
8180
const docsUrl = extension.docsUrl || undefined
8281
const downloadUrl = extension.resourceUrls?.download || undefined
83-
84-
const resourceUrls = transformResourceUrls(extension.resourceUrls)
85-
const communityUrls = transformCommunityUrls(extension.communityUrls)
82+
const pricingUrl = extension.resourceUrls?.pricing ?? undefined
8683

8784
// Generate JSON-LD schema
8885
const schema = await generateSoftwareDetailSchema({
@@ -158,9 +155,9 @@ export default async function ExtensionPage({
158155

159156
{relatedProducts.length > 0 && <RelatedProducts products={relatedProducts} />}
160157

161-
<ProductPricing pricing={extension.pricing} pricingUrl={resourceUrls.pricing} />
158+
<ProductPricing pricing={extension.pricing} pricingUrl={pricingUrl} />
162159

163-
<ProductLinks resourceUrls={resourceUrls} communityUrls={communityUrls} />
160+
<ProductLinks resourceUrls={extension.resourceUrls} communityUrls={extension.communityUrls} />
164161

165162
<ProductCommands install={extension.installCommand} launch={extension.launchCommand} />
166163

src/app/[locale]/ides/[slug]/page.tsx

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import { getGithubStars } from '@/lib/generated/github-stars'
1515
import { translateLicenseText } from '@/lib/license'
1616
import { generateSoftwareDetailMetadata } from '@/lib/metadata'
1717
import { generateSoftwareDetailSchema } from '@/lib/metadata/schemas'
18-
import { transformCommunityUrls, transformResourceUrls } from '@/lib/product-utils'
1918

2019
export const revalidate = 3600
2120

@@ -71,13 +70,11 @@ export default async function IDEPage({
7170
const t = await getTranslations({ locale, namespace: 'pages.ideDetail' })
7271
const tGlobal = await getTranslations({ locale })
7372

74-
// Transform URLs
73+
// Transform URLs for component props
7574
const websiteUrl = ide.websiteUrl || ide.resourceUrls?.download || undefined
7675
const docsUrl = ide.docsUrl || undefined
7776
const downloadUrl = ide.resourceUrls?.download || undefined
78-
79-
const resourceUrls = transformResourceUrls(ide.resourceUrls)
80-
const communityUrls = transformCommunityUrls(ide.communityUrls)
77+
const pricingUrl = ide.resourceUrls?.pricing ?? undefined
8178

8279
// Generate JSON-LD schema
8380
const schema = await generateSoftwareDetailSchema({
@@ -138,9 +135,9 @@ export default async function IDEPage({
138135

139136
{relatedProducts.length > 0 && <RelatedProducts products={relatedProducts} />}
140137

141-
<ProductPricing pricing={ide.pricing} pricingUrl={resourceUrls.pricing} />
138+
<ProductPricing pricing={ide.pricing} pricingUrl={pricingUrl} />
142139

143-
<ProductLinks resourceUrls={resourceUrls} communityUrls={communityUrls} />
140+
<ProductLinks resourceUrls={ide.resourceUrls} communityUrls={ide.communityUrls} />
144141

145142
<ProductCommands install={ide.installCommand} launch={ide.launchCommand} />
146143

src/app/[locale]/ides/page.tsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import type { Locale } from '@/i18n/config'
22
import { generateListPageMetadata } from '@/lib/metadata'
3+
import type { LocalePageProps } from '@/types/locale'
34
import IDEsPageClient from './page.client'
45

56
export const revalidate = 3600
67

7-
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) {
8+
export async function generateMetadata({ params }: LocalePageProps) {
89
const { locale } = await params
910

1011
return await generateListPageMetadata({
@@ -14,11 +15,7 @@ export async function generateMetadata({ params }: { params: Promise<{ locale: s
1415
})
1516
}
1617

17-
type Props = {
18-
params: Promise<{ locale: string }>
19-
}
20-
21-
export default async function IDEsPage({ params }: Props) {
18+
export default async function IDEsPage({ params }: LocalePageProps) {
2219
const { locale } = await params
2320
return <IDEsPageClient locale={locale} />
2421
}

0 commit comments

Comments
 (0)