Fail production builds on TypeScript errors - #215
Open
arandomogg wants to merge 1 commit into
Open
Conversation
next.config.mjs set typescript.ignoreBuildErrors, so `next build` emitted deployment artifacts even when the project did not type-check. On a codebase spanning payments, KYC, contract, and admin boundaries, that turned type drift into runtime failures against real records instead of a failed build. Removing the flag surfaced eleven pre-existing errors, all from one source: the handler `defineRoute` returns declared its Next.js context argument as optional and loosely typed, which does not satisfy the RouteContext contract Next generates under .next/types. The argument is now the exported NextRouteContext type, declared exactly as the framework passes it. Callers that construct a handler directly pass an explicit empty-params context. Gate changes: - `npm run typecheck` runs with --incremental false so a stale .tsbuildinfo cannot let a check pass by reusing an earlier result. - `npm run typecheck:gate` (scripts/check-typecheck-gate.ts) fails if the suppression flags return or a file-level nocheck directive appears, and proves the gate still bites by introducing a deliberate type error and requiring tsc to reject it. - CI runs the gate on pull requests and on main, before the build step. Compiler strictness is unchanged: no blanket ignore, no file-level nocheck, and no relaxed tsconfig option replaces the removed flag. docs/type-safety.md records the gate, its stages, and the one standing exception (skipLibCheck, which covers only third-party declaration files in node_modules).
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.
closes #178
Problem
next.config.mjssettypescript.ignoreBuildErrors: true, sonext buildproduced deployment artifacts even when the project did not type-check. Across
payments, KYC, contract, and admin boundaries that meant a broken API contract
or an unsafe refactor could ship, with the type error only showing up at runtime
against real records.
What changed
The flag is gone.
next buildnow fails on any TypeScript error. A commentin
next.config.mjspoints at the rationale so it does not get reintroduced byreflex.
The errors it was hiding are fixed, not suppressed. Removing the flag
surfaced eleven errors across nine route files, all from a single source: the
handler
defineRoutereturns declared its Next.js context argument asnextContext?: { params?: Promise<Record<string, string>> | Record<string, string> }.That does not satisfy the
RouteContextcontract Next generates under.next/types— the argument is required there, andparamsis always apromise. The signature is now the exported
NextRouteContexttype, declaredexactly as the framework passes it:
parseParamsstill tolerates a missing context at runtime, since directcallers such as tests may omit it; only the exported handler signature is
strict. Tests that invoke a handler directly now pass an explicit empty-params
context, mirroring how Next actually calls them.
No compiler option was relaxed, no
@ts-nocheckwas added, and no blanketignore replaces the removed flag.
The gate is deterministic.
npm run typechecknow runstsc --noEmit --incremental false. A stale.tsbuildinfo— easy to hit acrossCI caches and developer machines — can otherwise let a check pass by reusing an
earlier result.
The gate is verified, not assumed.
npm run typecheck:gate(
scripts/check-typecheck-gate.ts) asserts two things:next.config.mjsenables neithertypescript.ignoreBuildErrorsnoreslint.ignoreDuringBuilds..ts/.tsxfile carries a file-level@ts-nocheck— the samefailure mode as the removed flag, with a narrower blast radius. (678 files
scanned today, none suppressed.)
a probe file under
lib/(inside the tsconfig include, so the compileractually sees it), runs
tsc, requires a non-zero exit naming the probe, anddeletes the probe again in a
finally.Check 3 is what keeps this honest over time: it fails if strictness or the
tsconfig include list is ever weakened to the point where such an error would
slip through.
CI runs the gate. A
TypeScript build gatestep runs in both thepull-request and main-branch jobs, after
typecheckand beforebuild, so nodeployment artifact is accepted without it.
Note that
npm run typecheckruns before.next/typesexists and thereforecannot see the generated route contracts —
npm run buildis the step thatchecks those. Both run on every PR; neither replaces the other. This is written
down in
docs/type-safety.md.Documentation
docs/type-safety.md— why the flag was dangerous, the four gate stages andwhat each covers, how to verify the gate, and what is not an acceptable
suppression. It documents the one standing exception,
skipLibCheck, whichapplies only to third-party declaration files in
node_modulesand does notweaken checking of any first-party code.
CONTRIBUTING.md— the pre-PR checklist now saysnpm run typecheckinsteadof a raw
npx tsc --noEmit, and states that the gate must not be disabled.Verification
npm run lintreact-hooks/exhaustive-depswarnings, untouched)npm run typechecknpm run typecheck:gateignoreBuildErrorsis restorednpm run test:contractsnpm run openapi:checknpm run buildVerified negatively as well: restoring
typescript.ignoreBuildErrors: truemakes
npm run typecheck:gatefail with a message naming the flag, and agenuinely broken source file failed
next buildat theRunning TypeScriptstep during development of this change.
Affected areas
defineRoutehandler signature)