Skip to content

feat: add configurable homepage SEO with admin settings - #6530

Open
seven7763 wants to merge 1 commit into
QuantumNous:mainfrom
seven7763:feat/homepage-seo
Open

feat: add configurable homepage SEO with admin settings#6530
seven7763 wants to merge 1 commit into
QuantumNous:mainfrom
seven7763:feat/homepage-seo

Conversation

@seven7763

@seven7763 seven7763 commented Jul 29, 2026

Copy link
Copy Markdown

📝 变更描述 / Description

为首页和公开页面添加完整的 SEO 管理功能,支持管理员通过后台配置所有 SEO 相关参数。

后端:

  • 新增 7 个 SEO 配置项(Title, TitleSuffix, Description, Keywords, SiteURL, OGImage, RobotsIndex)
  • 新增 /robots.txt 动态生成,支持管理员开关爬虫索引
  • 新增 /sitemap.xml 动态生成,含 lastmod / changefreq / priority
  • /api/status 暴露 SEO 字段供前端消费

前端:

  • 新增 lib/seo 模块:document meta tag 管理(title, description, OG, Twitter Card, canonical, JSON-LD)
  • 新增 useHomeSeo hook:首页长尾标题 + Schema.org 结构化数据
  • 新增 SEO 管理 section:7 个配置字段 + RobotsIndex 开关
  • 启动和路由切换时自动应用 SEO 配置
  • index.html 添加静态 fallback meta tags(不执行 JS 的爬虫也能抓取)

特性:

  • 长尾标题支持(完整标题覆盖 / 后缀拼接两种模式)
  • 按路由控制 robots(公开页 index、控制台 noindex)
  • JSON-LD 结构化数据(WebSite + Organization)
  • 保存 SEO 设置后立即刷新前端 status 缓存
  • i18n:7 种语言翻译

🚀 变更类型 / Type of change

  • ✨ 新功能 (New feature)

✅ 提交前检查项 / Checklist

  • 人工确认: 描述基于对代码逻辑的理解撰写。
  • 非重复提交: 已搜索确认无重复。
  • 变更理解: 已理解所有更改的工作原理。
  • 范围聚焦: 仅包含 SEO 相关代码。
  • 本地验证: Go build 和 frontend build 均通过。
  • 安全合规: 无敏感凭据。

📸 运行证明 / Proof of Work

# Go build
$ go build ./...   ✓ (zero errors)

# Frontend build
$ bun run build    ✓ (zero errors)

# robots.txt
GET /robots.txt →
User-agent: *
Allow: /
Disallow: /console
Disallow: /api/
Sitemap: https://example.com/sitemap.xml

# sitemap.xml
GET /sitemap.xml → 6 URLs with lastmod/changefreq/priority

此 PR 由 AI 辅助生成代码。

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added configurable SEO settings (title, suffix, description, keywords, site URL, OG image, and robots indexing) in system settings.
    • Added dynamic /robots.txt and /sitemap.xml endpoints based on configuration.
    • Enhanced browser SEO on load/navigation (document title, meta tags, Open Graph/Twitter cards) including JSON-LD, with language-aware defaults and homepage-specific behavior.
    • Added localized SEO configuration labels and help text.
  • Bug Fixes
    • Improved fallback behavior when SEO inputs are missing to reliably use system branding values.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Adds configurable SEO settings, robots.txt and sitemap.xml endpoints, frontend metadata and JSON-LD handling, homepage SEO behavior, administration controls, and localized SEO interface text.

Changes

SEO configuration and delivery

Layer / File(s) Summary
Backend SEO configuration
common/seo.go, model/option.go, controller/misc.go
Defines SEO defaults and configuration persistence, then exposes SEO values through the status response.
Robots and sitemap endpoints
controller/seo.go, router/main.go
Registers crawler routes and generates robots.txt and sitemap.xml responses from configured URLs and indexing state.
Browser SEO engine
web/src/lib/seo/*
Adds typed SEO inputs, localized title defaults, DOM metadata helpers, URL normalization, path classification, metadata application, cached status parsing, and JSON-LD cleanup.
Frontend SEO lifecycle
web/src/main.tsx, web/src/routes/__root.tsx, web/src/features/home/*
Applies SEO from cached or refreshed status data, updates metadata on navigation, and adds homepage-specific titles and structured data.
SEO administration settings
web/src/features/system-settings/general/system-info-section.tsx, web/src/features/system-settings/site/*, web/src/features/system-settings/hooks/use-update-option.ts, web/src/features/system-settings/types.ts
Adds SEO form fields, indexing controls, typed site settings, defaults, and status refresh handling.
Static metadata and translations
web/index.html, web/src/i18n/locales/*
Updates static SEO/social metadata and adds localized SEO configuration labels and descriptions.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Admin
  participant SettingsAPI
  participant StatusAPI
  participant Browser
  participant SEOEngine
  Admin->>SettingsAPI: Save SEO options
  SettingsAPI->>StatusAPI: Persist and expose SEO fields
  Browser->>StatusAPI: Load cached or refreshed status
  StatusAPI-->>Browser: System and SEO metadata
  Browser->>SEOEngine: Apply document SEO
  SEOEngine-->>Browser: Update title, meta tags, links, and JSON-LD
Loading

Possibly related PRs

Suggested reviewers: mrchi

Poem

A bunny found titles blooming bright,
With sitemap paths beneath moonlight.
Robots now know which way to hop,
Meta tags sparkle at every stop.
SEO carrots for every site! 🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding configurable SEO controls exposed through admin settings.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web/src/features/system-settings/general/system-info-section.tsx (1)

140-151: 🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick win

Map SEO dot-path fields to backend option keys before saving.

SEO form fields are registered as seo.title, seo.titleSuffix, seo.description, seo.keywords, seo.siteUrl, seo.ogImage, and seo.robotsIndex. useSettingsForm flattens these to the same keys, and this section passes them through unchanged, but the backend option contract reads/writes SEO.Title, SEO.TitleSuffix, SEO.Description, SEO.Keywords, SEO.SiteURL, SEO.OGImage, and SEO.RobotsIndex. Save the correct option key for each SEO field so these settings are written into common.SEOTitle and the matching global SEO values.

🤖 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 `@web/src/features/system-settings/general/system-info-section.tsx` around
lines 140 - 151, Update the onSubmit handler to map each flattened SEO field key
to its backend option key before calling updateOption.mutateAsync: map
seo.title, seo.titleSuffix, seo.description, seo.keywords, seo.siteUrl,
seo.ogImage, and seo.robotsIndex to the corresponding SEO.Title,
SEO.TitleSuffix, SEO.Description, SEO.Keywords, SEO.SiteURL, SEO.OGImage, and
SEO.RobotsIndex keys; leave non-SEO keys and existing value normalization
unchanged.
🧹 Nitpick comments (2)
web/src/features/system-settings/general/system-info-section.tsx (1)

65-65: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add URL validation for seo.siteUrl / seo.ogImage, matching Logo.

Logo enforces z.string().url().optional().or(z.literal('')), but seo.siteUrl and seo.ogImage are plain optional strings despite being URL-typed fields consumed by controller/seo.go for the sitemap <loc> base and Open Graph image. Invalid values would silently propagate into public robots/sitemap output.

Also applies to: 440-458, 462-478

🤖 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 `@web/src/features/system-settings/general/system-info-section.tsx` at line 65,
Update the validation schema containing siteUrl and the related seo.ogImage
field to use the same URL-or-empty-string optional validation as Logo:
z.string().url().optional().or(z.literal('')). Preserve the existing optional
behavior and apply the validation consistently to both SEO URL fields.
web/src/features/home/hooks/use-home-seo.ts (1)

16-23: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract a shared "read cached SEO status" helper instead of duplicating the localStorage read+parse logic three times. All three sites implement the identical pattern: read localStorage.getItem('status'), JSON.parse it, and silently swallow errors — a single helper (e.g. readCachedStatus() in web/src/lib/seo/) would remove the duplication and prevent the copies from drifting (e.g. inconsistent key names or error handling) as this code evolves.

  • web/src/features/home/hooks/use-home-seo.ts#L16-L23: replace this mount-time read with a call to the shared helper.
  • web/src/features/home/hooks/use-home-seo.ts#L61-L66: replace this cleanup-time read with the same shared helper.
  • web/src/routes/__root.tsx#L57-L64: replace this read with the same shared helper.
🤖 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 `@web/src/features/home/hooks/use-home-seo.ts` around lines 16 - 23, Extract
the duplicated localStorage status read and JSON parsing into a shared
readCachedStatus helper, preserving silent error handling and the existing
null/default behavior. Replace both reads in useHomeSeo and the read in the root
route with this helper; update web/src/features/home/hooks/use-home-seo.ts lines
16-23 and 61-66, and web/src/routes/__root.tsx lines 57-64.
🤖 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 `@controller/seo.go`:
- Around line 15-24: Protect all reads of shared SEO globals in seoSiteBase and
RobotsTxt with common.OptionMapRWMutex.RLock/RUnlock, matching the locking
convention used by updateOptionMap and GetStatus. Cover SEOSiteURL,
SEORobotsIndex, and the additional SEO reads at the referenced locations while
preserving their existing behavior.
- Line 85: Escape the configured site URL before inserting it into the `<loc>`
element in the sitemap generation flow around `b.WriteString`. Use
XML-appropriate escaping, such as `xml.EscapeText`, for `site` while preserving
the existing path concatenation and output structure.

In `@web/src/i18n/locales/fr.json`:
- Around line 4126-4132: Add translations for all missing SEO field labels and
hint strings defined in en.json to fr.json, ja.json, ru.json, vi.json,
zh-TW.json, and zh.json, including the listed indexing, canonical URL, keywords,
title, suffix, description, and social-preview text. Keep the locale JSON valid
and use native translations so these entries no longer fall back to English.

In `@web/src/i18n/locales/zh-TW.json`:
- Line 321: Update the “Allow Search Engine Indexing” translation in the locale
data to use the existing Traditional Chinese terminology, changing “搜索” to “搜尋”
while preserving the rest of the translation.

In `@web/src/lib/seo/apply.ts`:
- Around line 72-78: Refactor the language selection in the SEO application
logic to remove the nested ternary, extracting it into a clearly named camelCase
helper. Preserve the existing behavior for Chinese regional variants, mapping
TW/HK to zh-TW, other zh languages to zh-CN, and non-Chinese languages to their
original value.

In `@web/src/routes/__root.tsx`:
- Line 45: Update the SEO effect in the root route component to obtain the
active pathname through TanStack Router’s useRouterState with a
location.pathname selector, then use that pathname in the effect and dependency
array. Remove the direct window.location.pathname read and ensure
applySeoFromStatus reruns only when the active route pathname changes.

---

Outside diff comments:
In `@web/src/features/system-settings/general/system-info-section.tsx`:
- Around line 140-151: Update the onSubmit handler to map each flattened SEO
field key to its backend option key before calling updateOption.mutateAsync: map
seo.title, seo.titleSuffix, seo.description, seo.keywords, seo.siteUrl,
seo.ogImage, and seo.robotsIndex to the corresponding SEO.Title,
SEO.TitleSuffix, SEO.Description, SEO.Keywords, SEO.SiteURL, SEO.OGImage, and
SEO.RobotsIndex keys; leave non-SEO keys and existing value normalization
unchanged.

---

Nitpick comments:
In `@web/src/features/home/hooks/use-home-seo.ts`:
- Around line 16-23: Extract the duplicated localStorage status read and JSON
parsing into a shared readCachedStatus helper, preserving silent error handling
and the existing null/default behavior. Replace both reads in useHomeSeo and the
read in the root route with this helper; update
web/src/features/home/hooks/use-home-seo.ts lines 16-23 and 61-66, and
web/src/routes/__root.tsx lines 57-64.

In `@web/src/features/system-settings/general/system-info-section.tsx`:
- Line 65: Update the validation schema containing siteUrl and the related
seo.ogImage field to use the same URL-or-empty-string optional validation as
Logo: z.string().url().optional().or(z.literal('')). Preserve the existing
optional behavior and apply the validation consistently to both SEO URL fields.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f74ea30c-0965-48bf-a153-21dcf0f7e133

📥 Commits

Reviewing files that changed from the base of the PR and between 66ee6b8 and 3518df7.

📒 Files selected for processing (28)
  • common/seo.go
  • controller/misc.go
  • controller/seo.go
  • model/option.go
  • router/main.go
  • web/index.html
  • web/src/features/home/hooks/index.ts
  • web/src/features/home/hooks/use-home-seo.ts
  • web/src/features/home/index.tsx
  • web/src/features/system-settings/general/system-info-section.tsx
  • web/src/features/system-settings/hooks/use-update-option.ts
  • web/src/features/system-settings/site/index.tsx
  • web/src/features/system-settings/site/section-registry.tsx
  • web/src/features/system-settings/types.ts
  • web/src/i18n/locales/en.json
  • web/src/i18n/locales/fr.json
  • web/src/i18n/locales/ja.json
  • web/src/i18n/locales/ru.json
  • web/src/i18n/locales/vi.json
  • web/src/i18n/locales/zh-TW.json
  • web/src/i18n/locales/zh.json
  • web/src/lib/seo/apply.ts
  • web/src/lib/seo/defaults.ts
  • web/src/lib/seo/dom.ts
  • web/src/lib/seo/index.ts
  • web/src/lib/seo/types.ts
  • web/src/main.tsx
  • web/src/routes/__root.tsx

Comment thread controller/seo.go
Comment thread controller/seo.go Outdated
Comment on lines +4126 to +4132
"SEO Description": "Description SEO",
"SEO Keywords": "Mots-clés SEO",
"SEO OG Image URL": "URL image OG SEO",
"SEO Settings": "Paramètres SEO",
"SEO Site URL": "URL du site SEO",
"SEO Title (full)": "Titre SEO (complet)",
"SEO Title Suffix": "Suffixe du titre SEO",

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check which locale files are missing the new SEO keys present in en.json.

for f in web/src/i18n/locales/*.json; do
  echo "=== $f ==="
  for key in "Allow Search Engine Indexing" "Canonical base URL for Open Graph and sitemap" "Comma-separated keywords for search engines" "Used for meta description and social previews" "When disabled, robots.txt will block all crawlers"; do
    grep -q "\"$key\"" "$f" || echo "MISSING: $key"
  done
done

Repository: QuantumNous/new-api

Length of output: 1681


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the locale files and extract JSON key names safely with Python.
for f in web/src/i18n/locales/*.json; do
  echo "=== $f ==="
  python3 - <<'PY' "$f"
import json, sys
from pathlib import Path
p = Path(sys.argv[1])
keys = json.loads(p.read_text(encoding="utf-8")).keys()
seo_keys = [k for k in keys if "SEO" in k or k in {
  "Allow Search Engine Indexing",
  "Appended after system name when full title is empty: {Name} - {Suffix}",
  "Canonical base URL for Open Graph and sitemap",
  "Comma-separated keywords for search engines",
  "e.g. AI大模型API网关|OpenAI兼容|统一分发平台",
  "Full browser tab title. Supports long-tail keywords. Overrides suffix mode when set.",
  "Image shown in social media link previews",
  "Leave empty to use System Name + suffix",
  "Used for meta description and social previews",
  "When disabled, robots.txt will block all crawlers",
}]
# Sort deterministically without assuming locale string collation
seo_keys.sort(key=lambda s: s.encode("utf-8"))
missing = [
  "Allow Search Engine Indexing",
  "Appended after system name when full title is empty: {Name} - {Suffix}",
  "Canonical base URL for Open Graph and sitemap",
  "Comma-separated keywords for search engines",
  "e.g. AI大模型API网关|OpenAI兼容|统一分发平台",
  "Full browser tab title. Supports long-tail keywords. Overrides suffix mode when set.",
  "Image shown in social media link previews",
  "Leave empty to use System Name + suffix",
  "Used for meta description and social previews",
  "When disabled, robots.txt will block all crawlers",
]
print("FOUND:", "\n".join(seo_keys) if seo_keys else "")
print("MISSING:", "\n".join([k for k in missing if k not in keys]) or "None")
print("TOTAL_FOUND:", len(seo_keys))
print("TOTAL_MISSING:", len([k for k in missing if k not in keys]))
PY
done

Repository: QuantumNous/new-api

Length of output: 4164


🏁 Script executed:

#!/bin/bash
set -euo pipefail

target=web/src/i18n/locales/en.json
echo "Locales:"
git ls-files web/src/i18n/locales | sort
echo
echo "en.json line count/size:"
wc -l "$target"
echo
echo "Search target keys in en.json with context:"
while IFS= read -r line; do
  key=$(printf '%s' "$line" | sed -n 's/^[[:space:]]*"\([^"]*\)"[[:space:]]*:.*/\1/p')
  if test -n "$key"; then
    echo "--- $key"
  fi
done < <(grep -n -A1 -B1 -E 'SEO|Search Engine|Open Graph|sitemap|robots|meta description|keywords|canonical|Appended after system name|Overrides suffix mode|social previews|AI大模型API网关|System Name \+ suffix' "$target" | sed -n '1,220p')

Repository: QuantumNous/new-api

Length of output: 578


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Locales without reports:"
git ls-files web/src/i18n/locales | grep -v '^web/src/i18n/locales/_reports/' | sort

echo
echo "Raw key matches across locale JSON files:"
python3 - <<'PY'
from pathlib import Path
target = Path("web/src/i18n/locales/en.json")
all_keys = set()
missing = []
for p in sorted(Path("web/src/i18n/locales").glob("*.json")):
    if p.name in {"_reports", "_reports.json"}:
        continue
    if p.stem == "_sync-report":
        continue
    try:
        import json
        data = json.loads(p.read_text(encoding="utf-8"))
    except Exception as e:
        print(f"ERROR {p}: {e}")
        data = {}
    keys = set(data.get("") or data.keys())
    print(f"=== {p} ({len(keys)} keys) ===")
    for key in sorted(keys):
        print(repr(key))

target_keys = set(json.loads(target.read_text(encoding="utf-8")).keys())
candidates = [
    "SEO Description",
    "SEO Keywords",
    "SEO OG Image URL",
    "SEO Settings",
    "SEO Site URL",
    "SEO Title (.full)",
    "SEO Title Suffix",
    "Allow Search Engine Indexing",
    "Appended after system name when full title is empty: {Name} - {Suffix}",
    "Canonical base URL for Open Graph and sitemap",
    "Comma-separated keywords for search engines",
    "e.g. AI大模型API网关|OpenAI兼容|统一分发平台",
    "Full browser tab title. Supports long-tail keywords. Overrides suffix mode when set.",
    "Image shown in social media link previews",
    "Leave empty to use System Name + suffix",
    "Used for meta description and social previews",
    "When disabled, robots.txt will block all crawlers",
]
for name, p in (("en", target), *[ (str(p), p) for p in sorted(Path("web/src/i18n/locales").glob("*.json")) if p.name not in {"_reports", "_sync-report"} and "_reports" not in str(p) ]):
    try:
        import json
        keys = set(json.loads(p.read_text(encoding="utf-8")).keys())
    except Exception as e:
        print(f"{name}: error={e}")
        continue
    print(f"{name}: matches={sorted([k for k in candidates if k in keys])}")
PY

echo
echo "Context for likely new SEO keys in English locale:"
grep -n -C2 -E 'SEO Description|SEO Keywords|SEO OG Image URL|SEO Settings|SEO Site URL|SEO Title \(full\)|SEO Title Suffix|Allow Search Engine Indexing|Appended after system name when full title is empty|Canonical base URL for Open Graph|Comma-separated keywords for search engines|e\.g\. AI 大模型API网关|Full browser tab title|Image shown in social media|Leave empty to use System Name \+ suffix|Used for meta description and social previews|robots\.txt will block all crawlers' web/src/i18n/locales/en.json | sed -n '1,240p'

Repository: QuantumNous/new-api

Length of output: 6122


Add the missing SEO translation keys across the locales.

en.json defines the new SEO field labels/hints, but fr.json, ja.json, ru.json, vi.json, zh-TW.json, and zh.json are missing most or all of those keys, including:

  • "Allow Search Engine Indexing"
  • "Appended after system name when full title is empty: {Name} - {Suffix}"
  • "Canonical base URL for Open Graph and sitemap"
  • "Comma-separated keywords for search engines"
  • "e.g. AI大模型API网关|OpenAI兼容|统一分发平台"
  • "Full browser tab title. Supports long-tail keywords. Overrides suffix mode when set."
  • "Image shown in social media link previews"
  • "Leave empty to use System Name + suffix"
  • "Used for meta description and social previews"
  • "When disabled, robots.txt will block all crawlers"

Add translated values so these SEO UI text and hints do not fall back to the raw English labels.

🤖 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 `@web/src/i18n/locales/fr.json` around lines 4126 - 4132, Add translations for
all missing SEO field labels and hint strings defined in en.json to fr.json,
ja.json, ru.json, vi.json, zh-TW.json, and zh.json, including the listed
indexing, canonical URL, keywords, title, suffix, description, and
social-preview text. Keep the locale JSON valid and use native translations so
these entries no longer fall back to English.

"Allow requests to private IP ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)": "允許請求私有 IP 範圍 (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)",
"Allow Retry": "允許重試",
"Allow safety_identifier passthrough": "允許透傳 safety_identifier",
"Allow Search Engine Indexing": "允許搜索引擎索引",

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

Use the locale’s existing Traditional Chinese terminology.

This value uses “搜索”, while the locale consistently uses “搜尋” for search-related UI. Change it to 允許搜尋引擎索引.

🤖 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 `@web/src/i18n/locales/zh-TW.json` at line 321, Update the “Allow Search Engine
Indexing” translation in the locale data to use the existing Traditional Chinese
terminology, changing “搜索” to “搜尋” while preserving the rest of the translation.

Comment thread web/src/lib/seo/apply.ts
Comment on lines +72 to +78
if (lang) {
document.documentElement.lang = lang.startsWith('zh')
? lang.toLowerCase().includes('tw') || lang.toLowerCase().includes('hk')
? 'zh-TW'
: 'zh-CN'
: 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.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Nested ternary violates coding guideline — extract to a named helper.

The two-level nested ternary for document.documentElement.lang is exactly the pattern the repo guideline prohibits.

As per coding guidelines, "禁止两层及以上嵌套三元表达式;复杂逻辑应拆分为小函数,命名应有意义并遵循驼峰命名。"

♻️ Proposed refactor
+function normalizeHtmlLang(lang: string): string {
+  if (!lang.startsWith('zh')) return lang
+  const lower = lang.toLowerCase()
+  return lower.includes('tw') || lower.includes('hk') ? 'zh-TW' : 'zh-CN'
+}
+
 if (lang) {
-    document.documentElement.lang = lang.startsWith('zh')
-      ? lang.toLowerCase().includes('tw') || lang.toLowerCase().includes('hk')
-        ? 'zh-TW'
-        : 'zh-CN'
-      : lang
+    document.documentElement.lang = normalizeHtmlLang(lang)
 }
📝 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
if (lang) {
document.documentElement.lang = lang.startsWith('zh')
? lang.toLowerCase().includes('tw') || lang.toLowerCase().includes('hk')
? 'zh-TW'
: 'zh-CN'
: lang
}
function normalizeHtmlLang(lang: string): string {
if (!lang.startsWith('zh')) return lang
const lower = lang.toLowerCase()
return lower.includes('tw') || lower.includes('hk') ? 'zh-TW' : 'zh-CN'
}
if (lang) {
document.documentElement.lang = normalizeHtmlLang(lang)
}
🤖 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 `@web/src/lib/seo/apply.ts` around lines 72 - 78, Refactor the language
selection in the SEO application logic to remove the nested ternary, extracting
it into a clearly named camelCase helper. Preserve the existing behavior for
Chinese regional variants, mapping TW/HK to zh-TW, other zh languages to zh-CN,
and non-Chinese languages to their original value.

Source: Coding guidelines

Comment thread web/src/routes/__root.tsx
} from '@/lib/auth-session'
import { subscribeAuthSessionEvents } from '@/lib/auth-session-sync'
import { resolveLegacyRoute } from '@/lib/legacy-route'
import { applySeoFromStatus } from '@/lib/seo'

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n '"`@tanstack/react-router`"' web/package.json
rg -n 'useRouterState' web/src -g '*.tsx' -g '*.ts'

Repository: QuantumNous/new-api

Length of output: 798


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- package lock snippets ---\n'
for f in web/package-lock.json web/bun.lockb web/bun.lock web/pnpm-lock.yaml web/yarn.lock package-lock.json bun.lockb bun.lock pnpm-lock.yaml yarn.lock; do
  if [ -f "$f" ]; then
    echo "FILE $f"
    rg -n '"`@tanstack/react-router`"|`@tanstack/react-router`' "$f" | head -80 || true
  fi
done

printf '\n--- root route outline and relevant source ---\n'
ast-grep outline web/src/routes/__root.tsx 2>/dev/null || true
wc -l web/src/routes/__root.tsx
cat -n web/src/routes/__root.tsx | sed -n '1,130p'

printf '\n--- package manager files ---\n'
git ls-files | rg '(^web/package\.json$|^web/bun\.lock$|^web/bun\.lockb$|^package-lock\.json$|^bun\.lock$)' | sed 's#^`#-` #'

Repository: QuantumNous/new-api

Length of output: 248


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- package lock snippets ---'
for f in web/package-lock.json web/bun.lockb web/bun.lock web/pnpm-lock.yaml web/yarn.lock package-lock.json bun.lockb bun.lock pnpm-lock.yaml yarn.lock; do
  if [ -f "$f" ]; then
    echo "FILE $f"
    rg -n '"`@tanstack/react-router`"|`@tanstack/react-router`' "$f" | head -120 || true
  fi
done

printf '%s\n' ''
printf '%s\n' '--- root route outline and relevant source ---'
ast-grep outline web/src/routes/__root.tsx 2>/dev/null || true
wc -l web/src/routes/__root.tsx
cat -n web/src/routes/__root.tsx | sed -n '1,160p'

printf '%s\n' ''
printf '%s\n' '--- package manager files ---'
git ls-files | rg '(^web/package\.json$|^web/bun\.lock$|^web/bun\.lockb$|^package-lock\.json$|^bun\.lock$)' || true

printf '%s\n' ''
printf '%s\n' '--- search for applySeoFromStatus and status-related SEO usage ---'
rg -n 'applySeoFromStatus|status.*SEO|noindex|robots|title' web/src/routes/__root.tsx web/src -g '*.tsx' -g '*.ts' | head -200

Repository: QuantumNous/new-api

Length of output: 24578


🌐 Web query:

@tanstack/react-router 1.170.18 useRouterState docs

💡 Result:

The useRouterState hook in TanStack Router allows you to subscribe to and access the current internal state of the router [1][2]. Usage and Best Practices: Because useRouterState exposes the entire router state, it can incur a performance cost [2]. The official documentation advises that for common tasks like accessing the current location or active route matches, you should prefer more specific, performant, and ergonomic hooks such as useLocation and useMatches [1][3]. Options: The hook accepts an optional object to configure its behavior: - select: A selector function that receives the RouterState and returns a specific slice of state. Using this is recommended to ensure your component only re-renders when the selected piece of state changes [1][4]. - structuralSharing: A boolean that configures whether structural sharing is enabled for the selected value [1][3]. This defaults to the router's global configuration [4]. Example: import { useRouterState } from '@tanstack/react-router'; function LoadingIndicator { // Use select to subscribe only to the relevant slice of state const isLoading = useRouterState({ select: (s) => s.isLoading }); return isLoading?

Loading...
: null; } In server-side rendering (SSR) environments, useRouterState retrieves the current router state synchronously without subscribing to store updates [4].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- web package.json router entries ---'
cat -n web/package.json | sed -n '1,120p'

printf '%s\n' ''
printf '%s\n' '--- router hook examples in repo ---'
for f in web/src/components/page-transition.tsx web/src/components/navigation-progress.tsx web/src/components/layout/components/public-header.tsx; do
  if [ -f "$f" ]; then
    echo "FILE $f"
    cat -n "$f" | sed -n '1,130p'
  fi
done

Repository: QuantumNous/new-api

Length of output: 15880


Reapply SEO when the active route changes, not on every render.

The SEO effect has no dependency array and still reads window.location.pathname outside React state, so it only runs on incidental re-renders and can miss metadata changes for navigation. Subscribe to TanStack Router’s active URL via useRouterState({ select: (s) => s.location.pathname }) and use that value with the effect dependency.

🤖 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 `@web/src/routes/__root.tsx` at line 45, Update the SEO effect in the root
route component to obtain the active pathname through TanStack Router’s
useRouterState with a location.pathname selector, then use that pathname in the
effect and dependency array. Remove the direct window.location.pathname read and
ensure applySeoFromStatus reruns only when the active route pathname changes.

@seven7763
seven7763 force-pushed the feat/homepage-seo branch from 3518df7 to d65612e Compare July 29, 2026 20:01

@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: 1

🤖 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 `@web/src/lib/seo/cached-status.ts`:
- Around line 5-8: Update readCachedStatus to validate the JSON.parse result
before returning it: accept only non-null objects that are not arrays, and
return null for primitives or arrays. Remove reliance on the unchecked
Record<string, unknown> cast while preserving the existing localStorage read and
error-handling behavior.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: fbf6c5a8-dddc-444f-9c0b-02f11729557d

📥 Commits

Reviewing files that changed from the base of the PR and between 3518df7 and d65612e.

📒 Files selected for processing (29)
  • common/seo.go
  • controller/misc.go
  • controller/seo.go
  • model/option.go
  • router/main.go
  • web/index.html
  • web/src/features/home/hooks/index.ts
  • web/src/features/home/hooks/use-home-seo.ts
  • web/src/features/home/index.tsx
  • web/src/features/system-settings/general/system-info-section.tsx
  • web/src/features/system-settings/hooks/use-update-option.ts
  • web/src/features/system-settings/site/index.tsx
  • web/src/features/system-settings/site/section-registry.tsx
  • web/src/features/system-settings/types.ts
  • web/src/i18n/locales/en.json
  • web/src/i18n/locales/fr.json
  • web/src/i18n/locales/ja.json
  • web/src/i18n/locales/ru.json
  • web/src/i18n/locales/vi.json
  • web/src/i18n/locales/zh-TW.json
  • web/src/i18n/locales/zh.json
  • web/src/lib/seo/apply.ts
  • web/src/lib/seo/cached-status.ts
  • web/src/lib/seo/defaults.ts
  • web/src/lib/seo/dom.ts
  • web/src/lib/seo/index.ts
  • web/src/lib/seo/types.ts
  • web/src/main.tsx
  • web/src/routes/__root.tsx
🚧 Files skipped from review as they are similar to previous changes (23)
  • web/src/lib/seo/index.ts
  • web/src/lib/seo/types.ts
  • web/src/routes/__root.tsx
  • web/src/features/home/index.tsx
  • web/src/i18n/locales/vi.json
  • web/src/main.tsx
  • router/main.go
  • web/src/lib/seo/defaults.ts
  • web/src/lib/seo/dom.ts
  • web/src/i18n/locales/ru.json
  • web/src/features/system-settings/site/index.tsx
  • web/src/i18n/locales/zh-TW.json
  • controller/misc.go
  • model/option.go
  • common/seo.go
  • web/src/i18n/locales/ja.json
  • controller/seo.go
  • web/src/features/system-settings/general/system-info-section.tsx
  • web/src/features/system-settings/hooks/use-update-option.ts
  • web/src/lib/seo/apply.ts
  • web/index.html
  • web/src/features/home/hooks/use-home-seo.ts
  • web/src/i18n/locales/en.json

Comment thread web/src/lib/seo/cached-status.ts Outdated
Add a complete SEO management system for the homepage and public pages:

Backend:
- Add SEO option variables (Title, TitleSuffix, Description, Keywords,
  SiteURL, OGImage, RobotsIndex) with admin persistence
- Add /robots.txt handler with configurable crawl rules and sitemap link
- Add /sitemap.xml handler with lastmod, changefreq, and priority metadata
- Expose SEO fields in /api/status for frontend consumption

Frontend:
- Add lib/seo module for document meta tag management (title, description,
  keywords, OG, Twitter Card, canonical, JSON-LD)
- Add useHomeSeo hook for long-tail SEO titles and structured data on homepage
- Add SEO Settings admin section with all fields + RobotsIndex toggle
- Apply SEO from status on boot and route navigation
- Add static fallback meta tags in index.html for crawlers without JS

Features:
- Long-tail title support (full title override or suffix mode)
- Per-route robots control (public pages indexed, console pages noindex)
- JSON-LD structured data (WebSite + Organization) on homepage
- Admin toggle to block all crawlers via robots.txt
- Immediate status cache refresh on SEO setting changes

i18n: translations added for en, zh, zh-TW, ja, fr, ru, vi

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@seven7763
seven7763 force-pushed the feat/homepage-seo branch from d65612e to bd8850e Compare July 29, 2026 20:15

@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: 1

🤖 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 `@web/src/i18n/locales/en.json`:
- Line 1467: The English locale’s SEO placeholder currently uses Chinese-only
text; replace the value for the key “e.g. AI大模型API网关|OpenAI兼容|统一分发平台” with an
English or language-neutral SEO example while leaving the locale key and
surrounding translations unchanged.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 38c1536f-ef57-4076-a867-c998911807f4

📥 Commits

Reviewing files that changed from the base of the PR and between d65612e and bd8850e.

📒 Files selected for processing (29)
  • common/seo.go
  • controller/misc.go
  • controller/seo.go
  • model/option.go
  • router/main.go
  • web/index.html
  • web/src/features/home/hooks/index.ts
  • web/src/features/home/hooks/use-home-seo.ts
  • web/src/features/home/index.tsx
  • web/src/features/system-settings/general/system-info-section.tsx
  • web/src/features/system-settings/hooks/use-update-option.ts
  • web/src/features/system-settings/site/index.tsx
  • web/src/features/system-settings/site/section-registry.tsx
  • web/src/features/system-settings/types.ts
  • web/src/i18n/locales/en.json
  • web/src/i18n/locales/fr.json
  • web/src/i18n/locales/ja.json
  • web/src/i18n/locales/ru.json
  • web/src/i18n/locales/vi.json
  • web/src/i18n/locales/zh-TW.json
  • web/src/i18n/locales/zh.json
  • web/src/lib/seo/apply.ts
  • web/src/lib/seo/cached-status.ts
  • web/src/lib/seo/defaults.ts
  • web/src/lib/seo/dom.ts
  • web/src/lib/seo/index.ts
  • web/src/lib/seo/types.ts
  • web/src/main.tsx
  • web/src/routes/__root.tsx
🚧 Files skipped from review as they are similar to previous changes (27)
  • web/src/features/system-settings/site/section-registry.tsx
  • web/src/features/home/hooks/index.ts
  • web/index.html
  • web/src/routes/__root.tsx
  • web/src/lib/seo/cached-status.ts
  • web/src/i18n/locales/ja.json
  • web/src/lib/seo/types.ts
  • web/src/lib/seo/index.ts
  • web/src/i18n/locales/fr.json
  • router/main.go
  • web/src/features/system-settings/site/index.tsx
  • web/src/features/system-settings/hooks/use-update-option.ts
  • web/src/features/system-settings/types.ts
  • web/src/features/system-settings/general/system-info-section.tsx
  • common/seo.go
  • web/src/i18n/locales/vi.json
  • web/src/lib/seo/defaults.ts
  • web/src/main.tsx
  • web/src/features/home/hooks/use-home-seo.ts
  • controller/seo.go
  • controller/misc.go
  • web/src/features/home/index.tsx
  • web/src/i18n/locales/zh-TW.json
  • web/src/i18n/locales/zh.json
  • web/src/lib/seo/dom.ts
  • web/src/lib/seo/apply.ts
  • model/option.go

"Duration Unit": "Duration Unit",
"Duration Value": "Duration Value",
"Dynamic Pricing": "Dynamic Pricing",
"e.g. AI大模型API网关|OpenAI兼容|统一分发平台": "e.g. AI大模型API网关|OpenAI兼容|统一分发平台",

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

Use an English example in the English locale.

This placeholder is displayed in the English SEO settings UI but contains only Chinese text, which is confusing for English-speaking administrators. Use an English or language-neutral SEO example instead.

Proposed fix
-    "e.g. AI大模型API网关|OpenAI兼容|统一分发平台": "e.g. AI大模型API网关|OpenAI兼容|统一分发平台",
+    "e.g. AI API gateway|OpenAI-compatible|unified distribution platform": "e.g. AI API gateway|OpenAI-compatible|unified distribution platform",
🤖 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 `@web/src/i18n/locales/en.json` at line 1467, The English locale’s SEO
placeholder currently uses Chinese-only text; replace the value for the key
“e.g. AI大模型API网关|OpenAI兼容|统一分发平台” with an English or language-neutral SEO
example while leaving the locale key and surrounding translations unchanged.

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.

1 participant