Skip to content

seo : configure conancial and hreflang alternates (#62)#140

Merged
DataDave-Dev merged 1 commit into
DataDave-Dev:mainfrom
YasserYG8:feat/seo-alternates
Jun 28, 2026
Merged

seo : configure conancial and hreflang alternates (#62)#140
DataDave-Dev merged 1 commit into
DataDave-Dev:mainfrom
YasserYG8:feat/seo-alternates

Conversation

@YasserYG8

@YasserYG8 YasserYG8 commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

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:

  1. Added SEO Utility: Created a reusable seo.ts helper function to construct canonical URLs and mapping links for all supported locales (en, es, pt, ar, fr, it, and 'x-default').
  2. Root Layout Configuration: Configured layout.tsx with dynamic metadata alternates to cover the homepage locale routes.
  3. Sub-Page Alternates: Integrated dynamic SEO alternates into:
    • Editor/App Page: app/page.tsx
    • Docs Pages: docs/[slug]/page.tsx
    • Graphs Page: graphs/page.tsx
    • Saved Graph Page: graphs/[id]/page.tsx

These additions ensure search engines correctly map translations and prevent duplicate-content dilution.


How to test / verify

  1. Run pnpm typecheck to confirm no TypeScript compilation regressions.
  2. Run pnpm test to verify unit tests continue to pass.
  3. Start the dev server (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

    • Improved multilingual page metadata so localized pages now include proper canonical and alternate-language links.
  • Bug Fixes

    • Updated several pages to generate metadata dynamically, keeping titles and descriptions consistent across languages and routes.
    • Enhanced SEO link handling to better support default-language and localized page discovery.

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds a new Chinese humanizer skill document and lock entry, and updates localized app, docs, graphs, and layout metadata to generate alternates from a shared SEO helper.

Changes

Humanizer skill bundle

Layer / File(s) Summary
Skill document and lock
.agents/skills/humanizer-zh/SKILL.md, skills-lock.json
Adds the humanizer-zh skill guidance, examples, checklist, scoring rubric, and references, and records the skill in the lock file.

Localized SEO metadata

Layer / File(s) Summary
Alternate-link helper
src/lib/seo.ts
Adds getAlternates to build canonical and locale-language URLs from the app base URL and configured locales.
Metadata wiring
src/app/[lang]/layout.tsx, src/app/[lang]/app/page.tsx, src/app/[lang]/docs/[slug]/page.tsx, src/app/[lang]/graphs/page.tsx, src/app/[lang]/graphs/[id]/page.tsx
Replaces static metadata exports with async generateMetadata functions that pass route-specific paths and languages into getAlternates.

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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • DataDave-Dev/weftmap issue 62 — The PR adds alternates and canonical metadata through generateMetadata across localized routes, matching the SEO fix described there.

Possibly related PRs

  • DataDave-Dev/weftmap#8 — Both PRs update localized docs page metadata generation in src/app/[lang]/docs/[slug]/page.tsx.

Poem

I nibbled the metadata carrots at dawn,
And hopped past the AI fluff, all gone.
The locale trail twinkles, neat and true,
With app and docs in a softer hue.
🐇 Hoppity-hum, the pages breathe anew.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description explains the change, but it doesn't follow the required template sections or checklist. Rewrite it using the required 'Qué hace', 'Tipo', and 'Checklist' sections, and include the missing status checkboxes.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title is related to the SEO alternates change and stays concise, despite the typo in "conancial".
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

Resolve the Prettier formatting failure.

CI reports prettier --check issues 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

📥 Commits

Reviewing files that changed from the base of the PR and between 656cd67 and 0fec454.

📒 Files selected for processing (8)
  • .agents/skills/humanizer-zh/SKILL.md
  • skills-lock.json
  • src/app/[lang]/app/page.tsx
  • src/app/[lang]/docs/[slug]/page.tsx
  • src/app/[lang]/graphs/[id]/page.tsx
  • src/app/[lang]/graphs/page.tsx
  • src/app/[lang]/layout.tsx
  • src/lib/seo.ts

Comment on lines +284 to +323
### 16. 标题中的标题大写

**问题:** AI 聊天机器人将标题中的所有主要单词大写。

**改写前:**
> ## 战略谈判与全球伙伴关系

**改写后:**
> ## 战略谈判与全球伙伴关系

**注:** 中文标题通常不涉及大小写问题,此模式在中文中不太适用。

---

### 17. 表情符号

**问题:** AI 聊天机器人经常用表情符号装饰标题或项目符号。

**改写前:**
> 🚀 **启动阶段:** 产品在第三季度发布
> 💡 **关键洞察:** 用户更喜欢简单
> ✅ **下一步:** 安排后续会议

**改写后:**
> 产品在第三季度发布。用户研究显示更喜欢简单。下一步:安排后续会议。

---

### 18. 弯引号

**问题:** ChatGPT 使用弯引号("")而不是直引号("")。

**改写前:**
> 他说"项目进展顺利",但其他人不同意。

**改写后:**
> 他说"项目进展顺利",但其他人不同意。

**注:** 中文通常使用中文引号(「」或""),此模式在中文中表现为英文引号的使用。

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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.

Suggested change
### 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

Comment on lines +13 to +23
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),
};
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 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.

Comment thread src/lib/seo.ts
languages["x-default"] = `${baseUrl}/en${cleanPath}`;

return {
canonical: `${baseUrl}/${currentLang}${cleanPath}`,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 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.

Suggested change
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.

@DataDave-Dev DataDave-Dev merged commit 4e7fd69 into DataDave-Dev:main Jun 28, 2026
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants