fix/site: Return HTTP 404 instead of 200 for unknown pages - #1860
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
marcleblanc2
enabled auto-merge (squash)
September 7, 2026 22:43
jac
approved these changes
Sep 9, 2026
marcleblanc2
force-pushed
the
fix-real-404-status
branch
from
September 9, 2026 16:46
9c29b39 to
cf8daf7
Compare
marcleblanc2
disabled auto-merge
September 9, 2026 16:48
marcleblanc2
enabled auto-merge (squash)
September 9, 2026 19:47
generateStaticParams returned {params: {slug}} (Pages Router shape), so the
App Router ignored it: no doc page was prerendered and every request rendered
on demand. notFound() then fired inside the root layout's <Suspense>, after
the response had started streaming, so missing pages were served with 200.
Return {slug} so all pages prerender, exclude the root document (served by
app/page.tsx), and set dynamicParams = false so unknown slugs are rejected
at the router with a 404. The existing not-found.tsx page is unchanged.
Amp-Thread-ID: https://ampcode.com/threads/T-01a07590-7d1a-72ec-826b-9081eed4f715
Co-authored-by: Amp <amp@ampcode.com>
The route is now fully prerendered, so it no longer runs as a serverless function and the timeout setting has no effect. Amp-Thread-ID: https://ampcode.com/threads/T-01a07590-7d1a-72ec-826b-9081eed4f715 Co-authored-by: Amp <amp@ampcode.com>
marcleblanc2
force-pushed
the
fix-real-404-status
branch
from
September 9, 2026 19:47
cf8daf7 to
8028aed
Compare
marcleblanc2
added a commit
that referenced
this pull request
Sep 11, 2026
Linear [FE-499: Fix doc site issues](https://linear.app/sourcegraph/issue/FE-499/fix-doc-site-issues) ## Problem - Our docs site has hundreds of broken links - `dev/check-links.mjs` finds broken internal links and anchors, but it isn't run automatically, so PRs can easily break links (renaming a heading, moving or deleting a page) without anyone noticing ## Solution - Updated the script to also work as a PR check, with additional functions beyond what's run when used as a CI test in Vercel builds - PR check to run the script and report if the PR breaks links - It runs the script (with `--check-anchors`) on both the PR head and its merge base, and diffs the findings - This catches both directions: - **Outbound**: a changed page links to a page or `#heading` that doesn't exist - **Inbound**: the PR renames a heading or removes/moves a page that other, unchanged pages link to — those show up as findings in files the PR didn't touch - Pre-existing broken links are ignored by the PR check - The comment is created / updated in place, and once the PR is fixed, the PR check passes and the comment is updated to say so - A PR that never broke anything gets no comment ## Verification PR check comment in test PR: #1895 (comment) ### Broken links found <img width="1826" height="1628" alt="Screenshot 2026-09-09 at 20 05 31" src="https://github.com/user-attachments/assets/930cde1f-50b1-46c0-9421-bd75a257c17d" /> ### Broken links fixed <img width="910" height="168" alt="Screenshot 2026-09-09 at 20 06 31" src="https://github.com/user-attachments/assets/1cc26276-5cba-4595-85fc-8e10955aabab" /> ## Absolute self-links and external links - Absolute links to this site (`https://sourcegraph.com/docs/…`, `http://…`, `//…`, `www.`, the legacy `https://docs.sourcegraph.com/…`) fail the check even when the target exists: they leave the Vercel preview and local dev, and hide moved pages behind redirects. The finding names the relative link, following `src/data/redirects.ts` when the page moved. Version-pinned links (`/@5.1/…`) stay external - External links on lines this PR added are requested (HEAD, then GET on an error status, following redirects); only 404 and 410 are findings, so rate limits, bot blocks, 5xx and network errors never fail a PR. Placeholder hosts (`*.example.com`, `localhost`, templated `<host>`) are skipped - Findings with a fix become one suggested-change review comment per line, which the author can apply from the PR. Suggestions already on the PR are not posted again - #1899 clears the 67 existing absolute self-links so this check starts from zero Test PR: #1900 (report comment + one review suggestion; the `#sampling` anchor deliberately does not exist, so that link gets no suggestion; a second run posted nothing new) ## Related - Draft PR #1562 proposes a daily Slack digest with a separate reimplementation of this script - Instead, this PR improves on the existing script, and gates PRs - PR #1860 enabled external link checkers to find broken links again ## Amp threads - [Broken link PR check](https://ampcode.com/threads/T-01a0753f-0f4f-7478-b36c-87466e7c0261) - [Asset case mismatch](https://ampcode.com/threads/T-01a07597-43c0-751b-8c49-6e5809e714d2) - [Docs - Fix broken heading links](https://ampcode.com/threads/T-01a07623-9d65-7356-96b8-2bebb31ffa5a) - [Self-links and external links](https://ampcode.com/threads/T-01a08a01-44c1-775b-84d0-d67ff9501905) --------- Co-authored-by: Amp <amp@ampcode.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Linear FE-499: Fix doc site issues
3 problems from the same root cause
https://sourcegraph.com/docs/this-page-does-not-existreturns HTTP 200. The "Page not found" UI renders, but only via the client-sideNEXT_NOT_FOUNDdigest. Any status-code-based link checker (lychee, W3C, etc.) therefore reports the docs site as 100% clean no matter how many links are dead.Root cause
Two things stacked up in
src/app/[...slug]/page.tsx:generateStaticParamsreturned{params: {slug}}, the Pages Router shape. App Router expects{slug}, so Next silently ignored it: zero doc pages were prerendered and every page has been rendered on demand by a serverless function since the site launched.notFound()runs inside the root layout's<Suspense>. By then the shell has already streamed with a 200, so Next can't change the status code.History
The site was always meant to be static; the function has been broken three different ways:
allPosts.map(post => { ({slug: ...}); })— block body, noreturn, yieldsundefinedper item.{params: {slug}}. PR body says "Previously Server routes are now SSG, speeding up page load times significantly." Wrong shape, so nothing changed.export const maxDuration = 300added to the route with no explanation. That's the Vercel function timeout — only needed because pages were rendering on demand and the big ones exceeded the default (see 25–30 s renders below).Fix
{slug}fromgenerateStaticParamsso all doc pages actually prerender (523 routes).flattenedPath === ''), which is served byapp/page.tsx. Including it fails the build withexport path '/' doesn't match '/[...slug]'.export const dynamicParams = falseso unknown slugs are rejected at the router with a real 404, before rendering/streaming starts.not-found.tsxis untouched; users see the same 404 page as before.maxDuration = 300is removed: the route no longer runs as a serverless function, so the timeout setting had nothing left to configure.No HTTP 404 response on missing pages
Before: HTTP 200

After: HTTP 404

Note: the additional HTTP 204 shown in this screenshot is the Vercel preview toolbar, won't be shown in prod
Proof: before vs after on Vercel
Every URL in
sitemap.xml(522) was fetched withcurl, capturingx-vercel-cache,x-matched-path,x-vercel-id,age, andtime_total.Before — a fresh deployment of
main(temporary branchcold-baseline-main, empty commit, since deleted), swept immediately after it went live so nothing was cached:x-vercel-cachex-matched-path/[...slug]on 520/520x-vercel-idpdx1::iad1::…(edge + function region) on 520/520time_totalmedian / p90 / maxSlowest cold renders:
/technical-changelog30.1 s,/self-hosted/observability/dashboards24.7 s, then a cluster at ~9 s (concurrent cold lambda starts). Both >15 s pages would 504 withoutmaxDuration = 300.Production today (
sourcegraph.com/docs): all 520 pages alreadyHIT,x-matched-path: /[...slug], function region present.agevalues cluster in three bursts 34.5–39 h old (deploy was 39 h earlier), i.e. crawlers walked the site and forced every page to render after the last deploy. Any never-requested URL is aMISSthat invokes the function and returns a 200 not-found page, which is then cached.After — this PR's preview, also a fresh deployment swept immediately after it went live:
x-vercel-cachex-matched-path/batch-changes/batch-spec-yaml-reference) 522/522x-vercel-idpdx1::…(edge only, no function region) 522/522time_totalmedian / p90 / maxx-matched-path: /404, static, no functionSide by side, first-visitor pass: median 0.48 s → 0.39 s, p90 0.68 s → 0.45 s, max 30.1 s → 1.2 s, pages over 2 s 10 → 0. Warm passes are identical (~0.19 s), which is why the on-demand rendering went unnoticed.
Other checks
dynamicParams = false)..mdrewrite → 200, trailing slash → 308,/@6.4/...→ 307 to versioned host,/api/og/...→ 200.next build: 526/526 static pages, ~22 s locally (Node 20.20.2). Was 9. Vercel preview build passed.npx tsc --noEmitclean.Results
revalidate, so pages were already cached until the next deploypreview: truefrontmatter are inallPosts, so they prerender andPreviewGuardstill gates them by?preview. None exist indocs/today, so that path is untested here.Amp threads