seo : configure conancial and hreflang alternates (#62)#140
Conversation
📝 WalkthroughWalkthroughThis PR adds a new Chinese humanizer skill document and lock entry, and updates localized app, docs, graphs, and layout metadata to generate ChangesHumanizer skill bundle
Localized SEO metadata
Sequence Diagram(s)sequenceDiagram
participant generateMetadata
participant getAlternates
participant locales
generateMetadata->>getAlternates: route path and lang
getAlternates->>locales: read configured locales
getAlternates-->>generateMetadata: canonical and language alternates
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/lib/seo.ts (1)
1-22: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winResolve the Prettier formatting failure.
CI reports
prettier --checkissues for this file. Run the formatter so the build passes.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/lib/seo.ts` around lines 1 - 22, The Prettier check is failing on getAlternates in seo.ts, so reformat the file to match the project’s Prettier rules without changing behavior. Focus on the getAlternates function and its returned object, and make sure the formatting of the imports, indentation, and object literals matches what prettier --check expects.Source: Pipeline failures
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.agents/skills/humanizer-zh/SKILL.md:
- Around line 284-323: The examples in the Chinese humanizer skill for
“标题中的标题大写” and “弯引号” are no-ops because the before/after text is effectively
unchanged, so they should not remain as English-only illustrations. Update the
relevant sections in SKILL.md by either removing these entries or replacing them
with Chinese-native examples that demonstrate a real rewrite, and keep the
guidance aligned with the existing pattern names and markdown structure around
those numbered sections.
In `@src/app/`[lang]/graphs/[id]/page.tsx:
- Around line 13-23: The saved graph metadata in generateMetadata is incorrectly
advertising hreflang alternates for a private, user-specific page. Update the
metadata for this route to avoid calling getAlternates for graphs/${id}, and
instead omit alternates entirely or add a robots noindex directive for the
auth-gated page. Keep the change localized to generateMetadata in the
saved-graph page so public routes that use getAlternates remain unaffected.
In `@src/lib/seo.ts`:
- Line 18: Normalize currentLang before constructing the canonical in
generateMetadata so unsupported locales do not produce self-referential
canonicals. Use the existing Locale/defaultLocale logic (matching the isLocale
guard used by the route handlers) to validate or fallback currentLang, then
build canonical from the sanitized locale in seo.ts; this also covers the
downstream generateMetadata callers that pass lang through unchanged.
---
Outside diff comments:
In `@src/lib/seo.ts`:
- Around line 1-22: The Prettier check is failing on getAlternates in seo.ts, so
reformat the file to match the project’s Prettier rules without changing
behavior. Focus on the getAlternates function and its returned object, and make
sure the formatting of the imports, indentation, and object literals matches
what prettier --check expects.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 58c43fb1-ce04-4075-bd95-525a2fdd339d
📒 Files selected for processing (8)
.agents/skills/humanizer-zh/SKILL.mdskills-lock.jsonsrc/app/[lang]/app/page.tsxsrc/app/[lang]/docs/[slug]/page.tsxsrc/app/[lang]/graphs/[id]/page.tsxsrc/app/[lang]/graphs/page.tsxsrc/app/[lang]/layout.tsxsrc/lib/seo.ts
| ### 16. 标题中的标题大写 | ||
|
|
||
| **问题:** AI 聊天机器人将标题中的所有主要单词大写。 | ||
|
|
||
| **改写前:** | ||
| > ## 战略谈判与全球伙伴关系 | ||
|
|
||
| **改写后:** | ||
| > ## 战略谈判与全球伙伴关系 | ||
|
|
||
| **注:** 中文标题通常不涉及大小写问题,此模式在中文中不太适用。 | ||
|
|
||
| --- | ||
|
|
||
| ### 17. 表情符号 | ||
|
|
||
| **问题:** AI 聊天机器人经常用表情符号装饰标题或项目符号。 | ||
|
|
||
| **改写前:** | ||
| > 🚀 **启动阶段:** 产品在第三季度发布 | ||
| > 💡 **关键洞察:** 用户更喜欢简单 | ||
| > ✅ **下一步:** 安排后续会议 | ||
|
|
||
| **改写后:** | ||
| > 产品在第三季度发布。用户研究显示更喜欢简单。下一步:安排后续会议。 | ||
|
|
||
| --- | ||
|
|
||
| ### 18. 弯引号 | ||
|
|
||
| **问题:** ChatGPT 使用弯引号("")而不是直引号("")。 | ||
|
|
||
| **改写前:** | ||
| > 他说"项目进展顺利",但其他人不同意。 | ||
|
|
||
| **改写后:** | ||
| > 他说"项目进展顺利",但其他人不同意。 | ||
|
|
||
| **注:** 中文通常使用中文引号(「」或""),此模式在中文中表现为英文引号的使用。 | ||
|
|
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Remove or adapt the English-only examples.
These two sections are effectively no-ops in Chinese: the before/after text is unchanged, so they don’t teach a real transformation and they add to the markdownlint noise already reported here. Either drop them or replace them with Chinese-native examples that actually rewrite text.
♻️ Suggested fix
-### 16. 标题中的标题大写
-
-**问题:** AI 聊天机器人将标题中的所有主要单词大写。
-
-**改写前:**
-> ## 战略谈判与全球伙伴关系
-
-**改写后:**
-> ## 战略谈判与全球伙伴关系
-
-**注:** 中文标题通常不涉及大小写问题,此模式在中文中不太适用。
+### 16. 标题大小写(仅适用于英文标题)
+
+**问题:** 英文标题常被错误地改成每个单词都首字母大写。
+
+**改写前:**
+> the strategic negotiations and global partnerships
+
+**改写后:**
+> The Strategic Negotiations and Global Partnerships📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ### 16. 标题中的标题大写 | |
| **问题:** AI 聊天机器人将标题中的所有主要单词大写。 | |
| **改写前:** | |
| > ## 战略谈判与全球伙伴关系 | |
| **改写后:** | |
| > ## 战略谈判与全球伙伴关系 | |
| **注:** 中文标题通常不涉及大小写问题,此模式在中文中不太适用。 | |
| --- | |
| ### 17. 表情符号 | |
| **问题:** AI 聊天机器人经常用表情符号装饰标题或项目符号。 | |
| **改写前:** | |
| > 🚀 **启动阶段:** 产品在第三季度发布 | |
| > 💡 **关键洞察:** 用户更喜欢简单 | |
| > ✅ **下一步:** 安排后续会议 | |
| **改写后:** | |
| > 产品在第三季度发布。用户研究显示更喜欢简单。下一步:安排后续会议。 | |
| --- | |
| ### 18. 弯引号 | |
| **问题:** ChatGPT 使用弯引号("")而不是直引号("")。 | |
| **改写前:** | |
| > 他说"项目进展顺利",但其他人不同意。 | |
| **改写后:** | |
| > 他说"项目进展顺利",但其他人不同意。 | |
| **注:** 中文通常使用中文引号(「」或""),此模式在中文中表现为英文引号的使用。 | |
| ### 16. 标题大小写(仅适用于英文标题) | |
| **问题:** 英文标题常被错误地改成每个单词都首字母大写。 | |
| **改写前:** | |
| > the strategic negotiations and global partnerships | |
| **改写后:** | |
| > The Strategic Negotiations and Global Partnerships | |
| --- | |
| ### 17. 表情符号 | |
| **问题:** AI 聊天机器人经常用表情符号装饰标题或项目符号。 | |
| **改写前:** | |
| > 🚀 **启动阶段:** 产品在第三季度发布 | |
| > 💡 **关键洞察:** 用户更喜欢简单 | |
| > ✅ **下一步:** 安排后续会议 | |
| **改写后:** | |
| > 产品在第三季度发布。用户研究显示更喜欢简单。下一步:安排后续会议。 | |
| --- | |
| ### 18. 弯引号 | |
| **问题:** ChatGPT 使用弯引号("")而不是直引号("")。 | |
| **改写前:** | |
| > 他说"项目进展顺利",但其他人不同意。 | |
| **改写后:** | |
| > 他说"项目进展顺利",但其他人不同意。 | |
| **注:** 中文通常使用中文引号(「」或""),此模式在中文中表现为英文引号的使用。 |
🧰 Tools
🪛 LanguageTool
[uncategorized] ~303-~303: 数词与名词之间一般应存在量词,可能缺少量词。
Context: ...装饰标题或项目符号。 改写前: > 🚀 启动阶段: 产品在第三季度发布 > 💡 关键洞察: 用户更喜欢简单 > ✅ 下一步: 安...
(wa5)
[uncategorized] ~308-~308: 数词与名词之间一般应存在量词,可能缺少量词。
Context: ...欢简单 > ✅ 下一步: 安排后续会议 改写后: > 产品在第三季度发布。用户研究显示更喜欢简单。下一步:安排后续会议。 --- ### 18....
(wa5)
🪛 markdownlint-cli2 (0.22.1)
[warning] 292-292: Multiple headings with the same content
(MD024, no-duplicate-heading)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.agents/skills/humanizer-zh/SKILL.md around lines 284 - 323, The examples in
the Chinese humanizer skill for “标题中的标题大写” and “弯引号” are no-ops because the
before/after text is effectively unchanged, so they should not remain as
English-only illustrations. Update the relevant sections in SKILL.md by either
removing these entries or replacing them with Chinese-native examples that
demonstrate a real rewrite, and keep the guidance aligned with the existing
pattern names and markdown structure around those numbered sections.
Source: Linters/SAST tools
| export async function generateMetadata({ | ||
| params, | ||
| }: { | ||
| params: Promise<{ lang: string; id: string }>; | ||
| }): Promise<Metadata> { | ||
| const { lang, id } = await params; | ||
| return { | ||
| title: "Weftmap — Saved graph", | ||
| alternates: getAlternates(`graphs/${id}`, lang), | ||
| }; | ||
| } |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win
Emitting hreflang alternates for an auth-gated, per-user saved graph is questionable.
Unlike the homepage/app/docs/graphs index routes, graphs/${id} is a private, user-specific resource gated by auth()/notFound() and is not translated content. Advertising canonical + per-locale languages here tells crawlers each locale serves an equivalent translated page, which isn't true and exposes the existence of specific graph IDs in head tags. Consider omitting alternates (or setting robots: { index: false }) for saved-graph pages rather than generating locale alternates.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/app/`[lang]/graphs/[id]/page.tsx around lines 13 - 23, The saved graph
metadata in generateMetadata is incorrectly advertising hreflang alternates for
a private, user-specific page. Update the metadata for this route to avoid
calling getAlternates for graphs/${id}, and instead omit alternates entirely or
add a robots noindex directive for the auth-gated page. Keep the change
localized to generateMetadata in the saved-graph page so public routes that use
getAlternates remain unaffected.
| languages["x-default"] = `${baseUrl}/en${cleanPath}`; | ||
|
|
||
| return { | ||
| canonical: `${baseUrl}/${currentLang}${cleanPath}`, |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win
currentLang flows into the canonical URL without validation.
generateMetadata runs before the page component's isLocale(lang)/notFound() check, so a request to an unsupported locale (e.g. /zz/app) still emits canonical: ${baseUrl}/zz/app, advertising a self-referential canonical for a 404. Consider normalizing currentLang to a valid Locale (falling back to defaultLocale) before building the canonical, mirroring the isLocale guard the routes already use.
🛡️ Proposed guard
-import { locales } from "`@/i18n/config`";
+import { locales, isLocale, defaultLocale } from "`@/i18n/config`";
export function getAlternates(path: string, currentLang: string) {
const baseUrl = process.env.NEXT_PUBLIC_APP_URL || "https://weftmap.com";
+ const lang = isLocale(currentLang) ? currentLang : defaultLocale;
// cleanPath should start with slash if path is not empty, otherwise empty string
const cleanPath = path ? `/${path}` : "";
@@
return {
- canonical: `${baseUrl}/${currentLang}${cleanPath}`,
+ canonical: `${baseUrl}/${lang}${cleanPath}`,
languages,
};
}This is the root cause for the unvalidated lang passed by the downstream generateMetadata callers in the metadata-wiring files.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| canonical: `${baseUrl}/${currentLang}${cleanPath}`, | |
| canonical: `${baseUrl}/${lang}${cleanPath}`, |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lib/seo.ts` at line 18, Normalize currentLang before constructing the
canonical in generateMetadata so unsupported locales do not produce
self-referential canonicals. Use the existing Locale/defaultLocale logic
(matching the isLocale guard used by the route handlers) to validate or fallback
currentLang, then build canonical from the sanitized locale in seo.ts; this also
covers the downstream generateMetadata callers that pass lang through unchanged.
Title
feat: add hreflang alternates and canonical URLs (#62)Description
This PR addresses the missing SEO alternates and canonical URLs across all pages, resolving issue #62.
Changes Made:
en,es,pt,ar,fr,it, and'x-default').These additions ensure search engines correctly map translations and prevent duplicate-content dilution.
How to test / verify
pnpm typecheckto confirm no TypeScript compilation regressions.pnpm testto verify unit tests continue to pass.pnpm dev) and inspect the generated<head>elements of pages to verify that<link rel="canonical">and<link rel="alternate" hreflang="...">tags are present and correctly mapped.Summary by CodeRabbit
New Features
Bug Fixes