Repository navigation
Fix blank production site from dev-only JSX runtime - #19
Merged
Merged
Conversation
The deployed site rendered nothing and threw on load:
TypeError: (0 , s(...).jsxDEV) is not a function
Two problems from the Babel 7 -> 8 upgrade combined to cause it.
Babel 8 changed @babel/preset-react's default `runtime` from "classic" to
"automatic", so JSX now compiles to imports from a react/jsx-*-runtime
module instead of `React.createElement`. Which runtime it picks depends on
Babel's environment, and Babel falls back to "development" whenever
BABEL_ENV and NODE_ENV are both unset — which is exactly the case during
`npm run build`, since webpack's `mode` only defines NODE_ENV inside the
bundle, not in the process babel-loader runs in. The production build was
therefore emitting `jsxDEV` calls, and React's production build of
jsx-dev-runtime deliberately exports no `jsxDEV`, so the first render threw
and left an empty page.
Setting NODE_ENV correctly then exposed the second problem: .babelrc's
`env.production` block pulls in a `minify` preset that is not, and never
was, in devDependencies. It had simply never been reached, because the
environment was always "development". Webpack's own minifier already
handles this, so the block is removed rather than replaced.
The webpack configs now set NODE_ENV to match their mode before
babel-loader sees it, which fixes the production runtime and keeps the
development build on the dev runtime.
Verified by loading the built bundle in a headless browser with the
iNaturalist API stubbed: it now renders, geolocation lists nearby places,
searching returns matches, and selecting one advances to the taxa step.
Before this change the same check produced an empty body and the TypeError
above.
`npm run build` compiling clean is what let this reach production, so the
build now also runs scripts/check-production-bundle.js, which fails if a
development-only React runtime is present in the emitted bundle.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J7nU5T9UJU7ydrRdENiKuQ
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.
The deployed site is currently blank. It throws on load:
This is fallout from the Babel 7 → 8 upgrade in #17. Two problems combined.
What happened
Babel 8 changed
@babel/preset-react's defaultruntimefrom"classic"to"automatic", so JSX now compiles to imports from areact/jsx-*-runtimemodule instead ofReact.createElement.Which runtime it picks depends on Babel's environment — and Babel falls back to
"development"wheneverBABEL_ENVandNODE_ENVare both unset. That is exactly the case duringnpm run build: webpack'smodeonly definesNODE_ENVinside the bundle, not in the processbabel-loaderruns in.So the production build emitted
jsxDEVcalls. React's production build ofjsx-dev-runtimedeliberately exports nojsxDEV, so the first render threw and left an empty page.Setting
NODE_ENVcorrectly then exposed a second, latent problem:.babelrc'senv.productionblock pulls in aminifypreset that is not, and never was, indevDependencies. It had simply never been reached, because the environment was always"development". Webpack's own minifier already covers this, so the block is removed rather than replaced.The fix
NODE_ENVto match their mode beforebabel-loadersees it. Production gets the production runtime; development keepsjsxDEVand its better error messages.env.production/minifyblock is removed from.babelrc.Why CI didn't catch it, and what now does
npm run buildcompiling successfully says nothing about whether the bundle runs — and nothing in CI ever loaded it. The tests are no-ops, and my browser check on the previous PR ran against the dev server, wherejsxDEVlegitimately exists. That's precisely the gap that let a blank page ship green.npm run buildnow also runsscripts/check-production-bundle.js, which fails the build if a development-only React runtime is present in the emitted output:I verified the guard fails on the broken bundle and passes on the fixed one.
Verification
Built the production bundle, served it, and loaded it headless with the iNaturalist API stubbed — the real (non-offline) code path, which had never been exercised end to end before:
TypeErrorAlso confirmed the dev server still compiles and mounts cleanly (3 children under
#root, no page errors), plusnpm run lintand the tests pass.Note
This guard is deliberately narrow — it catches this specific class of "bundle throws on load". A real headless smoke test in CI that loads the built page and asserts it rendered would catch the general case, but it means adding a browser to CI, so I left that as your call.
🤖 Generated with Claude Code
https://claude.ai/code/session_01J7nU5T9UJU7ydrRdENiKuQ
Generated by Claude Code