feat: add per-page SEO metadata, structured data, and AI-search crawler access#250
Conversation
β¦er access Every page except /stats and /courses previously fell back to the layout's generic title and description, so all 1,472 sitemap'd race pages looked like duplicates to search engines. - Add generateMetadata to race, result, athlete, and /races pages with unique titles, descriptions, and canonical URLs - Add metadataBase, a title template, OG siteName defaults, and Search Console verification (via GOOGLE_SITE_VERIFICATION env var) to the layout - Add SportsEvent JSON-LD to race pages and WebSite JSON-LD to the homepage - Unblock AI search / user-triggered crawlers (OAI-SearchBot, ChatGPT-User, PerplexityBot, Claude-Web) in robots.ts β they drive cited referrals and result pages no longer incur ISR write cost; training crawlers stay blocked - Retitle /stats and /courses to fit the new title template Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DM6tEZaDCx5PQgaxuV8Fh8
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
π WalkthroughWalkthroughThe application expands global and route-specific metadata, adds WebSite and SportsEvent JSON-LD, updates page copy and canonical URLs, and changes the AI crawler user-agent list used by ChangesSEO metadata and discoverability
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
π₯ Pre-merge checks | β 4 | β 1β Failed checks (1 warning)
β Passed checks (4 passed)
β¨ Finishing Touchesπ Generate docstrings
π§ͺ Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
π§Ή Nitpick comments (2)
app/src/app/courses/page.tsx (1)
4-9: π Maintainability & Code Quality | π΅ Trivial | π€ Low valueExplicitly define
openGraphfields to ensure correct social previews.Next.js metadata does not automatically map the top-level
titleanddescriptionintoog:titleandog:descriptionOpen Graph tags. To guarantee that links shared on social platforms unfurl with these specific page details (rather than missing them or falling back to the root layout), explicitly include anopenGraphobject, just as you did inraces/page.tsx.
app/src/app/courses/page.tsx#L4-L9: Extract the title/description into variables and addopenGraph: { title, description, url: "/courses" }to the exportedmetadata.app/src/app/stats/page.tsx#L6-L11: Extract the title/description into variables and addopenGraph: { title, description, url: "/stats" }to the exportedmetadata.π€ 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 `@app/src/app/courses/page.tsx` around lines 4 - 9, Update metadata in app/src/app/courses/page.tsx lines 4-9 by extracting the title and description into reusable variables and adding openGraph with those values and url "/courses"; apply the same change to app/src/app/stats/page.tsx lines 6-11 using url "/stats", preserving the existing page metadata.app/src/app/races/page.tsx (1)
7-17: π Maintainability & Code Quality | π΅ Trivial | π€ Low valueExtract
titleto a variable to avoid duplication.You can make the returned metadata object DRY by defining the title as a constant and reusing it for the Open Graph configuration, similar to how the
descriptionis already being reused.β»οΈ Proposed refactor
export function generateMetadata(): Metadata { const { raceCount, totalResults } = getGlobalStats(); + const title = "All IRONMAN & 70.3 Races"; const description = `Browse ${raceCount.toLocaleString()} IRONMAN and IRONMAN 70.3 races with ${totalResults.toLocaleString()} finisher results. Filter by distance and year, then dive into time distributions for any race.`; return { - title: "All IRONMAN & 70.3 Races", + title, description, alternates: { canonical: "/races" }, - openGraph: { title: "All IRONMAN & 70.3 Races", description, url: "/races" }, + openGraph: { title, description, url: "/races" }, }; }π€ 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 `@app/src/app/races/page.tsx` around lines 7 - 17, Update generateMetadata by extracting the repeated page title into a local constant, then reuse that title for both the returned metadata title and openGraph.title while preserving all existing metadata values.
π€ 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.
Nitpick comments:
In `@app/src/app/courses/page.tsx`:
- Around line 4-9: Update metadata in app/src/app/courses/page.tsx lines 4-9 by
extracting the title and description into reusable variables and adding
openGraph with those values and url "/courses"; apply the same change to
app/src/app/stats/page.tsx lines 6-11 using url "/stats", preserving the
existing page metadata.
In `@app/src/app/races/page.tsx`:
- Around line 7-17: Update generateMetadata by extracting the repeated page
title into a local constant, then reuse that title for both the returned
metadata title and openGraph.title while preserving all existing metadata
values.
βΉοΈ Review info
βοΈ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: c721134f-567e-4052-981f-61c64a1eca14
π Files selected for processing (9)
app/src/app/athlete/[slug]/page.tsxapp/src/app/courses/page.tsxapp/src/app/layout.tsxapp/src/app/page.tsxapp/src/app/race/[slug]/page.tsxapp/src/app/race/[slug]/result/[id]/page.tsxapp/src/app/races/page.tsxapp/src/app/robots.tsapp/src/app/stats/page.tsx
Summary
Every page except
/statsand/coursespreviously fell back to the layout's generic title and description, so all 1,472 sitemap'd race pages looked like duplicate pages to search engines. This PR implements the SEO foundation (P0) items:generateMetadataon race, result, athlete, and/racespages with unique titles, descriptions, and canonical URLsmetadataBase, a%s | TriTimestitle template, OpenGraphsiteName/urldefaults, and optional Search Console verification via aGOOGLE_SITE_VERIFICATIONenv var (no tag emitted when unset)SportsEventJSON-LD on race pages,WebSiteJSON-LD on the homepage/statsand/coursesto fit the new title template and add canonical URLsTesting
npm run test: 157/157 pass (includingimport-graph.test.tsβ/racesstill imports onlyraces.ts)npm run lint: cleannpm run build: green; all routes keep their intended rendering strategy (race pages SSG-on-demand, result/athlete pages dynamic)π€ Generated with Claude Code
https://claude.ai/code/session_01DM6tEZaDCx5PQgaxuV8Fh8
Generated by Claude Code
Summary by CodeRabbit