Repository navigation
Link calculator error pages to the visitor's country's reports - #1374
Merged
Merged
Conversation
Both calculator error pages linked everyone to /us/reports, so a UK user who hit an error landed on the US reports page. The calculator and workspace routes' page now takes the country from the route; the root page, which also shows for errors outside a country route, takes it from the first path segment. Anything that is not a supported country falls back to /us/reports. The link stays a plain <a>, so following it is a full page load. Its eslint-disable comment for @next/next/no-html-link-for-pages is gone: the rule only checks string-literal hrefs, so with an expression the directive was unused and failed lint at --max-warnings 0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Next.js decodes route params, so a request to /%75k/reports renders the UK calculator with countryId "uk", while usePathname() keeps the encoded path. The root error page compared the encoded segment and linked such a visitor to /us/reports. firstPathSegment now decodes the segment once, as Next.js does, and keeps a malformed one as it is. Found by the independent review of #1374. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis
marked this pull request as ready for review
October 20, 2026 20:25
Contributor
Author
|
Merge audit for head 623f3c1.
|
This was referenced Oct 20, 2026
This branch was successfully deployed
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.
What
The calculator's error pages linked every visitor to
/us/reports, so a UK user who hit an error was sent to the US Reports page. Now:[countryId]/(calculator)/error.tsx, which(workspace)/error.tsxre-exports): the country comes from the route,useParams().countryId.app/error.tsx): this page also shows for errors the segment pages don't catch, such as in the[countryId]or(calculator)layouts, or on a route outside a country. The country comes from the first path segment, decoded once as Next.js decodes route params (next/dist/shared/lib/router/utils/route-matcher.js:20), becauseusePathname()keeps the encoding: a request to/%75k/reportsrenders the UK calculator. A malformed escape, which Next.js rejects as a param, is kept as it is and so falls back to the US.countryIds(@/libs/countries), or no value, links to/us/reports. The shared helper iscalculator-app/src/app/reportsHref.ts.The link stays a plain
<a>, so following it is a full page load that discards the client state that may have caused the error. #1366'seslint-disable-next-line @next/next/no-html-link-for-pagescomment is removed. The rule skips anyhrefthat isn't a string literal (@next/eslint-plugin-next/dist/rules/no-html-link-for-pages.js:211), so oncehrefis an expression the directive goes unused, and an unused directive failsbun run lintat--max-warnings 0. The comment explaining the full page load is kept, and a test now fails if the link becomes anext/link.#1365 (still a draft) adds
isCountryIdtocountries.ts. This PR doesn't depend on it: it usescountryIds, which is on main. It addsfast-checkto app's devDependencies on the same line, with the samebun.lockline, as #1365, so the two PRs merge cleanly in either order.Invariants
Tested with fast-check properties as well as examples, in
app/src/tests/unit/calculator/ErrorPages.test.tsx:reportsHrefreturns the Reports page of a supported country.countryIds,reportsHref(id) === /${id}/reports.UK,uk,../uk,constructor), any non-string, and a list of segments all return/us/reports.firstPathSegmentreturns that country, including when any of its letters are percent-encoded in either hex case.firstPathSegmentnever throws, whatever mix of%, hex digits and slashes the path has.Verification
calculator-app:bun run lint,bun run format:checkandbun run typecheckpass.app: prettier check, eslint,tscand the full vitest suite pass (437 files; 5,380 passed, 10 skipped).The new tests catch the bug: against main's error pages, 14 of the 64 fail, exactly the non-US cases. Mutants fail too: swapping the
<a>fornext/linkfails the plain-link test; looking the country up in an object literal fails onconstructorand['uk']; not decoding the segment fails 7 tests; decoding without thetryfails 3.End to end on the calculator dev server, with temporary pages (not committed) that throw:
/uk/zz-throw-calculator/uk/reports/uk/zz-throw-workspace/uk/reports/uk/zz-throw-root/uk/reports/ca/zz-throw-root/ca/reports/%75k/zz-throw-root/uk/reports(was/us/reportsbefore 623f3c1)/zz-throw-outside/us/reportsClicking the link on the UK page was a full page load: a DOM marker was gone afterwards, and
performance.timeOriginand the navigation entry both changed to/uk/reports.Review
/%75k/…linked to/us/reports. Reproduced on the dev server, fixed in 623f3c1 with regression tests. It confirmed the rest: the hook contexts in Next 15.5.12, which page catches which errors, the lint reasoning, and that Return 404 for calculator paths whose first segment is not a country #1365 merges cleanly in either order.axiom: n/a: calculator UI routing, no policy logic
🤖 Generated with Claude Code