Skip to content

fix: return a real 404 for nonexistent athlete pages#257

Merged
rootulp merged 1 commit into
mainfrom
fix/athlete-soft-404
Jul 16, 2026
Merged

fix: return a real 404 for nonexistent athlete pages#257
rootulp merged 1 commit into
mainfrom
fix/athlete-soft-404

Conversation

@rootulp

@rootulp rootulp commented Jul 16, 2026

Copy link
Copy Markdown
Owner

Problem

A nonexistent athlete (e.g. /athlete/no-such-person--zz-x) returned HTTP 200 with the not-found UI — a soft 404. Search engines can index soft 404s as real pages, and the status was misleading. Follow-up to #256.

Root cause

app/src/app/athlete/[slug]/loading.tsx created an implicit Suspense boundary. That makes Next stream the shell with a 200 status before the page runs, so the page's notFound() could only render the not-found UI — it couldn't change the already-flushed status. (Confirmed via response headers: HTTP/1.1 200 OK + Transfer-Encoding: chunked for a miss.)

Fix

Remove the segment's loading.tsx so Next resolves the page (and notFound()) before flushing the status.

  • Missing athlete → real 404.
  • Existing athlete → 200, profile renders unchanged.
  • The edge shard fetch is fast, so the lost loading skeleton costs little. Added a comment on the route so it isn't reintroduced.

Testing

  • New e2e/athlete-404.spec.ts: asserts miss → 404 (+ not-found UI) and real → 200 (+ profile). Verified red with loading.tsx present, green without.
  • Full unit suite (198) + lint pass.

Note: the same soft-404 pattern exists on /race/[slug]/result/[id] (also has a loading.tsx). Left out of this PR to keep it small — happy to fix it separately (its Node CSV-parse wait makes the loading skeleton more valuable, so it's a slightly different tradeoff).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Fixed athlete pages for nonexistent athletes to return a true HTTP 404 status.
    • Preserved the “Page not found” message for missing athlete profiles.
    • Confirmed existing athlete profiles continue to return HTTP 200 and display correctly.
  • Tests

    • Added end-to-end coverage for both missing and existing athlete pages.

A loading.tsx at app/src/app/athlete/[slug]/ created an implicit Suspense
boundary, which made Next stream the shell with a 200 status before the
page's notFound() ran. So a nonexistent athlete returned HTTP 200 with the
not-found UI — a soft 404 that search engines can index as a real page.

Remove the segment's loading.tsx so Next resolves the page (and notFound())
before flushing the status. Missing athletes now return a real 404; existing
athletes still return 200. The edge shard fetch is fast, so the lost loading
skeleton costs little.

Add e2e/athlete-404.spec.ts as a regression guard (verified red with the
loading.tsx present, green without).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DaVB6YHWz94LTY8AGVc88Q
@rootulp rootulp self-assigned this Jul 16, 2026
@vercel

vercel Bot commented Jul 16, 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 16, 2026 7:20am

@rootulp
rootulp enabled auto-merge (squash) July 16, 2026 07:17
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c81fc37b-41f8-42db-8650-9c20db4ae91b

📥 Commits

Reviewing files that changed from the base of the PR and between 1deab37 and 2ab32a3.

📒 Files selected for processing (3)
  • app/e2e/athlete-404.spec.ts
  • app/src/app/athlete/[slug]/loading.tsx
  • app/src/app/athlete/[slug]/page.tsx
💤 Files with no reviewable changes (1)
  • app/src/app/athlete/[slug]/loading.tsx

📝 Walkthrough

Walkthrough

The athlete route removes its loading UI to prevent a soft 404, documents the rendering constraint, and adds Playwright coverage for missing and existing athlete responses.

Changes

Athlete route status handling

Layer / File(s) Summary
Preserve real athlete 404 responses
app/src/app/athlete/[slug]/loading.tsx, app/src/app/athlete/[slug]/page.tsx
The athlete loading component is removed, and the page documents that this prevents an implicit Suspense boundary from converting notFound() into an HTTP 200 response.
Validate missing and existing athletes
app/e2e/athlete-404.spec.ts
Playwright tests verify 404 and “Page not found” output for missing athletes, and 200 with “Rootul Patel” for an existing athlete.

Estimated code review effort: 1 (Trivial) | ~5 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 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: fixing soft 404s so nonexistent athlete pages return a real 404.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 fix/athlete-soft-404

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.

@rootulp
rootulp merged commit 6576048 into main Jul 16, 2026
7 checks passed
@rootulp
rootulp deleted the fix/athlete-soft-404 branch July 16, 2026 07:20
rootulp added a commit that referenced this pull request Jul 16, 2026
* feat: add course grouping helpers to races manifest layer

* feat: point course links to the new course detail page

* refactor: extract pure computeRaceStats from getRaceStats

* feat: add course result pooling and combined leaderboards

* feat: add per-edition course summary roll-up

* test: cover finishSeconds filter and second edition in course summary

* feat: add course detail page aggregating all editions

* fix: don't emit results metadata for an invalid course ?year

* fix: return a real 404 for nonexistent course pages

src/app/courses/loading.tsx created a Suspense boundary wrapping the
[course] child segment, so Next streamed the shell with HTTP 200 before
notFound() resolved (soft 404 for /courses/<bad-slug> and out-of-range
?year=). Moves the overview-only files (page.tsx, loading.tsx, and its
chart components) into a (overview) route group so the loading boundary
no longer wraps [course]. Same class of bug as /athlete/[slug], fixed in
PR #257.

Adds e2e/course-404.spec.ts mirroring athlete-404.spec.ts.
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