Skip to content

feat: add per-page SEO metadata, structured data, and AI-search crawler access#250

Merged
rootulp merged 1 commit into
mainfrom
claude/tritimes-adoption-strategy-eski14
Jul 15, 2026
Merged

feat: add per-page SEO metadata, structured data, and AI-search crawler access#250
rootulp merged 1 commit into
mainfrom
claude/tritimes-adoption-strategy-eski14

Conversation

@rootulp

@rootulp rootulp commented Jul 15, 2026

Copy link
Copy Markdown
Owner

Summary

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 duplicate pages to search engines. This PR implements the SEO foundation (P0) items:

  • Per-page metadata: generateMetadata on race, result, athlete, and /races pages with unique titles, descriptions, and canonical URLs
  • Layout defaults: metadataBase, a %s | TriTimes title template, OpenGraph siteName/url defaults, and optional Search Console verification via a GOOGLE_SITE_VERIFICATION env var (no tag emitted when unset)
  • Structured data: SportsEvent JSON-LD on race pages, WebSite JSON-LD on the homepage
  • robots.ts: unblock AI search / user-triggered crawlers (OAI-SearchBot, ChatGPT-User, PerplexityBot, Claude-Web) since they drive cited referrals and result pages no longer incur ISR write cost; training crawlers (GPTBot, ClaudeBot, CCBot, etc.) stay blocked
  • Retitle /stats and /courses to fit the new title template and add canonical URLs

Testing

  • npm run test: 157/157 pass (including import-graph.test.ts β€” /races still imports only races.ts)
  • npm run lint: clean
  • npm 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

  • New Features
    • Added richer page titles, descriptions, canonical URLs, and social sharing metadata across athlete, race, result, courses, races, stats, and site pages.
    • Added structured data for the website and race events to improve how pages appear in search results.
    • Added Google site verification support.
  • Updates
    • Revised crawler access rules for selected AI bots.
    • Updated courses and stats page descriptions to better reflect their content.

…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
@vercel

vercel Bot commented Jul 15, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
tritimes Ready Ready Preview, Comment Jul 15, 2026 8:45pm

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Review Change Stack

πŸ“ Walkthrough

Walkthrough

The 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 robots.ts.

Changes

SEO metadata and discoverability

Layer / File(s) Summary
Global and section metadata
app/src/app/layout.tsx, app/src/app/courses/page.tsx, app/src/app/stats/page.tsx, app/src/app/races/page.tsx
Global metadata now includes a base URL, title template, Open Graph settings, and Google verification; section pages update descriptions, titles, canonical URLs, and dynamic race statistics metadata.
Dynamic route metadata
app/src/app/athlete/..., app/src/app/race/...
Athlete, race, and race-result pages generate titles, descriptions, canonical URLs, and Open Graph fields from fetched route data.
JSON-LD structured data
app/src/app/page.tsx, app/src/app/race/[slug]/page.tsx
The home page emits WebSite JSON-LD, and race pages emit sanitized SportsEvent JSON-LD with optional date and location data.
Crawler directives
app/src/app/robots.ts
The documented crawler policy and blocked AI user-agent list are updated.<>

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

  • rootulp/tritimes#210: Both changes use the athlete profile route and its asynchronous profile-fetching flow.
  • rootulp/tritimes#213: Both changes modify the athlete profile route and its exported route behavior.
πŸš₯ Pre-merge checks | βœ… 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
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 (4 passed)
Check name Status Explanation
Description Check βœ… Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check βœ… Passed The title accurately summarizes the main changes: per-page SEO metadata, structured data, and crawler access updates.
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 docstrings
  • Create stacked PR
  • Commit on current branch
πŸ§ͺ Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/tritimes-adoption-strategy-eski14

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

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
app/src/app/courses/page.tsx (1)

4-9: πŸ“ Maintainability & Code Quality | πŸ”΅ Trivial | πŸ’€ Low value

Explicitly define openGraph fields to ensure correct social previews.

Next.js metadata does not automatically map the top-level title and description into og:title and og:description Open 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 an openGraph object, just as you did in races/page.tsx.

  • app/src/app/courses/page.tsx#L4-L9: Extract the title/description into variables and add openGraph: { title, description, url: "/courses" } to the exported metadata.
  • app/src/app/stats/page.tsx#L6-L11: Extract the title/description into variables and add openGraph: { title, description, url: "/stats" } to the exported metadata.
πŸ€– 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 value

Extract title to 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 description is 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

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between c3d4454 and 3bfd20e.

πŸ“’ Files selected for processing (9)
  • app/src/app/athlete/[slug]/page.tsx
  • app/src/app/courses/page.tsx
  • app/src/app/layout.tsx
  • app/src/app/page.tsx
  • app/src/app/race/[slug]/page.tsx
  • app/src/app/race/[slug]/result/[id]/page.tsx
  • app/src/app/races/page.tsx
  • app/src/app/robots.ts
  • app/src/app/stats/page.tsx

@rootulp
rootulp merged commit a98516e into main Jul 15, 2026
7 checks passed
@rootulp
rootulp deleted the claude/tritimes-adoption-strategy-eski14 branch July 15, 2026 20:53
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