Skip to content

Commit a38733d

Browse files
committed
fix: revise SEO with canonical and hreflang
1 parent b9baa2b commit a38733d

File tree

7 files changed

+122
-2
lines changed

7 files changed

+122
-2
lines changed

src/app/[language]/(home)/blog/[slug]/page.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import Link from 'next/link'
44
import { notFound } from 'next/navigation'
55

66
import { getMDXComponents } from '@/components'
7+
import { defaultLanguage, languages } from '@/i18n'
78
import { getLocalizedSources } from '@/lib'
89

910
export const revalidate = false
@@ -112,9 +113,25 @@ export async function generateMetadata(props: {
112113

113114
const image = `/api/og/blog/${slug}/open-graph.png?language=${language}`
114115

116+
// Build canonical URL
117+
const canonicalPath =
118+
language === defaultLanguage ? `/blog/${slug}` : `/${language}/blog/${slug}`
119+
120+
// Build hreflang alternates for all languages
121+
const languagesAlternates: Record<string, string> = {}
122+
for (const lang of languages) {
123+
const langPath =
124+
lang === defaultLanguage ? `/blog/${slug}` : `/${lang}/blog/${slug}`
125+
languagesAlternates[lang] = langPath
126+
}
127+
115128
return {
116129
title: page.data.title,
117130
description: page.data.description,
131+
alternates: {
132+
canonical: canonicalPath,
133+
languages: languagesAlternates,
134+
},
118135
openGraph: {
119136
images: image,
120137
},

src/app/[language]/(home)/blog/page.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { Metadata } from 'next'
2+
import { defaultLanguage, languages } from '@/i18n'
23
import { getLocalizedSources } from '@/lib'
34
import { BlogList } from './components'
45

@@ -35,9 +36,24 @@ export async function generateMetadata({
3536
}): Promise<Metadata> {
3637
const { language } = await params
3738

39+
// Build canonical URL
40+
const canonicalPath =
41+
language === defaultLanguage ? '/blog' : `/${language}/blog`
42+
43+
// Build hreflang alternates for all languages
44+
const languagesAlternates: Record<string, string> = {}
45+
for (const lang of languages) {
46+
languagesAlternates[lang] =
47+
lang === defaultLanguage ? '/blog' : `/${lang}/blog`
48+
}
49+
3850
return {
3951
title: 'Blog',
4052
description: 'Latest updates and insights from the YAMLResume team',
53+
alternates: {
54+
canonical: canonicalPath,
55+
languages: languagesAlternates,
56+
},
4157
openGraph: {
4258
images: `/api/og/blog/open-graph.png?language=${language}`,
4359
},

src/app/[language]/(home)/page.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import type { Metadata } from 'next'
12
import 'react-medium-image-zoom/dist/styles.css'
3+
import { defaultLanguage, languages } from '@/i18n'
24
import {
35
BuyMeACoffeeSection,
46
ComparisonSection,
@@ -32,3 +34,27 @@ export default function HomePage() {
3234
</main>
3335
)
3436
}
37+
38+
export async function generateMetadata({
39+
params,
40+
}: {
41+
params: Promise<{ language: string }>
42+
}): Promise<Metadata> {
43+
const { language } = await params
44+
45+
// Build canonical URL
46+
const canonicalPath = language === defaultLanguage ? '/' : `/${language}`
47+
48+
// Build hreflang alternates for all languages
49+
const languagesAlternates: Record<string, string> = {}
50+
for (const lang of languages) {
51+
languagesAlternates[lang] = lang === defaultLanguage ? '/' : `/${lang}`
52+
}
53+
54+
return {
55+
alternates: {
56+
canonical: canonicalPath,
57+
languages: languagesAlternates,
58+
},
59+
}
60+
}

src/app/[language]/docs/[[...slug]]/page.tsx

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
import type { Metadata } from 'next'
1010
import { notFound } from 'next/navigation'
1111
import { getMDXComponents, LLMCopyButton, ViewOptions } from '@/components'
12-
import { defaultLanguage } from '@/i18n'
12+
import { defaultLanguage, languages } from '@/i18n'
1313
import { getLocalizedSources } from '@/lib'
1414

1515
export const revalidate = false
@@ -124,9 +124,32 @@ export async function generateMetadata({
124124
if (!page) notFound()
125125

126126
const image = `/api/og/docs/${slug.join('/')}/open-graph.png?language=${language}`
127+
128+
// Build canonical URL
129+
const slugPath = slug.length > 0 ? `/${slug.join('/')}` : ''
130+
const canonicalPath =
131+
language === defaultLanguage
132+
? `/docs${slugPath}`
133+
: `/${language}/docs${slugPath}`
134+
135+
// Build hreflang alternates for all languages
136+
const languagesAlternates: Record<string, string> = {}
137+
for (const lang of languages) {
138+
const langSlugPath = slug.length > 0 ? `/${slug.join('/')}` : ''
139+
const langPath =
140+
lang === defaultLanguage
141+
? `/docs${langSlugPath}`
142+
: `/${lang}/docs${langSlugPath}`
143+
languagesAlternates[lang] = langPath
144+
}
145+
127146
return {
128147
title: page.data.title,
129148
description: page.data.description,
149+
alternates: {
150+
canonical: canonicalPath,
151+
languages: languagesAlternates,
152+
},
130153
openGraph: {
131154
images: image,
132155
},

src/app/robots.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import type { MetadataRoute } from 'next'
2+
import { siteConfig } from '@/config/site'
3+
4+
export default function robots(): MetadataRoute.Robots {
5+
return {
6+
rules: {
7+
userAgent: '*',
8+
allow: '/',
9+
},
10+
sitemap: `${siteConfig.url}/sitemap.xml`,
11+
}
12+
}

src/app/sitemap.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,65 +43,91 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
4343
...blogSourcePt.getPages(),
4444
]
4545

46+
const now = new Date()
47+
4648
return [
4749
{
4850
url: url('/'),
51+
lastModified: now,
4952
changeFrequency: 'monthly',
5053
priority: 1,
5154
},
5255
{
5356
url: url('/blog'),
57+
lastModified: now,
5458
changeFrequency: 'monthly',
5559
priority: 0.8,
5660
},
5761
{
5862
url: url('/docs'),
63+
lastModified: now,
5964
changeFrequency: 'monthly',
6065
priority: 0.8,
6166
},
6267
// Add locale-specific home pages
6368
{
6469
url: url('/zh-cn'),
70+
lastModified: now,
6571
changeFrequency: 'monthly',
6672
priority: 0.9,
6773
},
6874
{
6975
url: url('/zh-tw'),
76+
lastModified: now,
7077
changeFrequency: 'monthly',
7178
priority: 0.9,
7279
},
7380
{
7481
url: url('/ja'),
82+
lastModified: now,
7583
changeFrequency: 'monthly',
7684
priority: 0.9,
7785
},
7886
{
7987
url: url('/es'),
88+
lastModified: now,
89+
changeFrequency: 'monthly',
90+
priority: 0.9,
91+
},
92+
{
93+
url: url('/pt'),
94+
lastModified: now,
95+
changeFrequency: 'monthly',
96+
priority: 0.9,
97+
},
98+
{
99+
url: url('/fr'),
100+
lastModified: now,
80101
changeFrequency: 'monthly',
81102
priority: 0.9,
82103
},
83104
{
84105
url: url('/developer/cli/index.html'),
106+
lastModified: now,
85107
changeFrequency: 'monthly',
86108
priority: 0.8,
87109
},
88110
{
89111
url: url('/developer/core/index.html'),
112+
lastModified: now,
90113
changeFrequency: 'monthly',
91114
priority: 0.8,
92115
},
93116
{
94117
url: url('/developer/create-yamlresume/index.html'),
118+
lastModified: now,
95119
changeFrequency: 'monthly',
96120
priority: 0.8,
97121
},
98122
{
99123
url: url('/developer/json2yamlresume/index.html'),
124+
lastModified: now,
100125
changeFrequency: 'monthly',
101126
priority: 0.8,
102127
},
103128
{
104129
url: url('/developer/playground/index.html'),
130+
lastModified: now,
105131
changeFrequency: 'monthly',
106132
priority: 0.8,
107133
},

src/i18n/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export { defaultLanguage, i18nConfig, type Language } from './config'
1+
export { defaultLanguage, i18nConfig, type Language, languages } from './config'
22
export { useTranslations } from './hooks'
33
export { getTranslations } from './translations'
44
export { getLocalizedUrl } from './url'

0 commit comments

Comments
 (0)