feat(seo): improve SERP appearance with per-page metadata and snippet suppression#799
feat(seo): improve SERP appearance with per-page metadata and snippet suppression#799ImJustChew wants to merge 1 commit intomainfrom
Conversation
… suppression - Worker: inject page-specific title + description for all static pages (courses, timetable, today, calendar, bus, venues, sports-venues, chat, shops, apps, team, contribute) when bots visit, via STATIC_PAGE_METADATA - Worker: applyHreflang() helper now wired into handleBusPage and handleDepartmentPage (was missing), ensuring correct zh-TW/en alternates - Worker: buildCourseMetaData now returns zhUrl/enUrl; handleCourseDetailPage uses applyHreflang() instead of static hreflang attributes - error.tsx (root + mods-pages): add data-nosnippet so error messages (e.g. "Failed to fetch dynamically imported module: ...") are excluded from Google snippets - apps/page.tsx: add data-nosnippet to empty pinned-apps hint to prevent it appearing as sitelink description - SEOHead.tsx: fix hreflang → hrefLang (JSX attribute) and use zh-TW locale Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
courseweb-web | 94da594 | Commit Preview URL Branch Preview URL |
Apr 27 2026, 09:43 AM |
There was a problem hiding this comment.
Pull request overview
Improves bot-visible SEO signals by injecting per-route metadata into the HTML shell in the Cloudflare Worker, standardizing hreflang handling across more routes, and suppressing unwanted snippet text via data-nosnippet.
Changes:
- Add per-static-page
<title>/ description injection for bot requests viaSTATIC_PAGE_METADATAand a generic bot handler in the Worker. - Apply consistent
hreflangrewriting for course, department, and bus bot-served pages. - Add
data-nosnippetto error boundaries and the empty pinned-apps reminder; fixSEOHeadJSX attribute tohrefLang(and usezh-TW).
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| apps/web/worker.ts | Adds generic bot-page rewriting, per-page metadata table, and shared hreflang rewriting helper. |
| apps/web/src/components/SEOHead.tsx | Fixes JSX hreflang attribute usage by switching to hrefLang and updates locale tag to zh-TW. |
| apps/web/src/app/error.tsx | Adds data-nosnippet to suppress error page content from SERP snippets. |
| apps/web/src/app/[lang]/(mods-pages)/error.tsx | Adds data-nosnippet around route-level error boundary content. |
| apps/web/src/app/[lang]/(mods-pages)/apps/page.tsx | Adds data-nosnippet to the empty pinned-apps reminder text. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const pathname = url.pathname; | ||
| const lang = pathname.startsWith("/en/") || pathname === "/en" ? "en" : "zh"; | ||
|
|
||
| // Canonical = current path with no query params | ||
| const canonicalUrl = `https://nthumods.com${pathname}`; | ||
| const zhPath = pathname.replace(/^\/(zh|en)(\/|$)/, "/zh$2"); | ||
| const enPath = pathname.replace(/^\/(zh|en)(\/|$)/, "/en$2"); | ||
| const zhUrl = `https://nthumods.com${zhPath}`; | ||
| const enUrl = `https://nthumods.com${enPath}`; |
There was a problem hiding this comment.
handleGenericBotPage() sets canonicalUrl directly from pathname. For locale roots, this means /zh and /zh/ (and /en vs /en/) will produce different canonicals depending on the request URL, which can lead to duplicate indexing. Since this worker already treats /zh/ and /en/ as the canonical forms in the sitemap generation, consider normalizing pathname so locale roots always canonicalize to a single form (e.g. enforce a trailing slash).
| let rewriter = new HTMLRewriter().on('link[rel="canonical"]', { | ||
| element(el) { | ||
| el.setAttribute("href", canonicalUrl); | ||
| }, | ||
| }); | ||
|
|
||
| rewriter = applyHreflang(rewriter, zhUrl, enUrl, zhUrl); | ||
|
|
||
| if (meta) { |
There was a problem hiding this comment.
handleGenericBotPage() always rewrites , but it only rewrites meta[property="og:url"] when a pagePath exists in STATIC_PAGE_METADATA. For pages not in that table (e.g. /zh/settings), bots will see a canonical pointing at the page while og:url remains the generic index.html value (https://nthumods.com). Consider always rewriting og:url to canonicalUrl regardless of whether meta is found.
| const enUrl = `https://nthumods.com${enPath}`; | ||
|
|
||
| // Look up page-specific metadata by stripping the lang prefix | ||
| const pagePath = pathname.replace(/^\/(zh|en)/, "") || "/"; |
There was a problem hiding this comment.
handleGenericBotPage() looks up STATIC_PAGE_METADATA using the raw pathname after stripping the lang prefix. If a bot hits a trailing-slash variant (e.g. /zh/apps/), pagePath becomes "/apps/" and the lookup will miss, leaving the generic title/description. Consider normalizing pagePath by removing a trailing "/" (except for "/") before indexing STATIC_PAGE_METADATA.
| const pagePath = pathname.replace(/^\/(zh|en)/, "") || "/"; | |
| const rawPagePath = pathname.replace(/^\/(zh|en)/, "") || "/"; | |
| const pagePath = | |
| rawPagePath.length > 1 && rawPagePath.endsWith("/") | |
| ? rawPagePath.slice(0, -1) | |
| : rawPagePath; |


Summary
STATIC_PAGE_METADATA: all major static pages (/courses,/timetable,/today,/calendar,/bus,/venues,/sports-venues,/chat,/shops,/apps,/team,/contribute) now get page-specific<title>and<meta name="description">injected for bot requests, replacing the generic index.html placeholderdata-nosnippeton error boundaries: error message text (e.g. "Failed to fetch dynamically imported module: https://…") and the empty pinned-apps hint ("Pin your most used apps here!") were leaking into Google sitelink snippets — suppressed withdata-nosnippethandleBusPageandhandleDepartmentPagenow callapplyHreflang()like course pages do;SEOHead.tsxattribute corrected fromhreflang→hrefLangwithzh-TWlocaleTest plan
/zh/courses,/zh/timetable,/zh/bus, etc. with a bot UA (curl -A "Googlebot") and verify<title>and<meta description>matchSTATIC_PAGE_METADATA<link rel="alternate" hreflang="zh-TW">/hreflang="en"appear on all bot-served pagesdata-nosnippetattribute🤖 Generated with Claude Code