building vodou.

Your config-overrides.js is dead if Docker runs react-scripts

config-overrides.js held thread-loader, cache-loader and HardSourceWebpackPlugin, but Docker ran plain react-scripts build. A one-line canary proves it loads.

Chad Priest / / 4 min read

Our web app’s frontend had a file called config-overrides.js with three build speedups in it: thread-loader, cache-loader and HardSourceWebpackPlugin. None of them had ever run.

I found this in a Cursor session where I asked the obvious question: how do we make the React build faster? The file looked like someone had already done the work, so I had been treating the build as tuned. It wasn’t. It was a stock Create React App build with a file sitting next to it that did nothing.

react-scripts build never opens config-overrides.js

config-overrides.js is not a CRA feature. It’s a convention from react-app-rewired, usually with customize-cra on top. You replace react-scripts build with react-app-rewired build in package.json. The wrapper then loads your file, changes the webpack config and hands off to react-scripts.

Two things were true at the same time. First, several packages the file referenced, including react-app-rewired and customize-cra, were not in package.json. Second, the Dockerfile called plain react-scripts build. A require for a missing package normally crashes. This one never could, because nothing ever loaded the file it lived in. The build printed Compiled successfully. every time.

Marmelab’s post on overriding the CRA webpack config without ejecting gives the standard advice: pick rewired or CRACO and put your changes in its override file. That’s correct. It doesn’t cover the case where the override file is present and the wrapper isn’t, because nothing in that setup checks that the file was actually used. We didn’t reinstall rewired to bring the file back to life, either. The real build-speed fix is moving off react-scripts and webpack to Vite, not trimming dependencies.

Wrapper-loaded config fails by being ignored, not by erroring

This is a general failure class. A config file only takes effect when a specific loader reads it, but the real entrypoint (a Dockerfile, a CI job, a monorepo script) calls the underlying tool directly. craco.config.js behind a Dockerfile that runs react-scripts has the same bug. So does Docker Compose: an openclaw helper passed explicit -f flags, which turns off the automatic merge of docker-compose.override.yml, and containers came up missing every env var in it. The README for pnpm11-ci-guard states the problem directly: “None of these throw.”

Every config file must be loaded by the exact command production runs; if a canary that throws on load does not break that command, the file is dead.

To check your own repo, run the production path, not your local npm script:

# 1. What does the real entrypoint invoke?
grep -nE 'react-scripts|react-app-rewired|craco|vite|jest' Dockerfile* package.json .github/workflows/*.yml 2>/dev/null

# 2. Is the loader installed at all?
node -e "require.resolve('react-app-rewired')" && echo LOADER_PRESENT || echo LOADER_MISSING

# 3. Canary: make the file impossible to ignore, then build the way CI does
sed -i.bak '1i\
throw new Error("CANARY: config-overrides.js was loaded");' config-overrides.js
docker build --no-cache --progress=plain . 2>&1 | grep -E 'CANARY|Compiled'
mv config-overrides.js.bak config-overrides.js

Passing looks like Error: CANARY: config-overrides.js was loaded and a failed build. Failing looks like Compiled successfully. from a build that should have blown up. Tools with a config-dump flag give you a shortcut: npx jest --showConfig, docker compose config, BABEL_SHOW_CONFIG_FOR=src/index.js.

A GOTCHA filed under “does config-overrides.js do anything?”

I didn’t write that finding down. Vodou did. It was running in the background of the Cursor session. It pulled out the fact, tagged it GOTCHA and saved it in a SQLite database on my laptop. It also saved the fact under the questions it answers, and one of them is literally “does config-overrides.js do anything?” So when a later session asks a sideways version, like “why is the Docker build slow,” retrieval matches on the question, not only on shared words. Search is hybrid (vector plus keyword), with a cross-encoder reranker and a precision floor that injects nothing when it would otherwise inject a guess.

That matters because bugs like this get rediscovered. The file is still in the repo, still looking configured. My next session might be Claude Code in a terminal, or ChatGPT in a browser tab while I plan the Vite migration. The same memory reaches both: hooks and MCP for the IDE and CLI, and the Vodou Bridge extension for the web chats. When the migration lands, the new fact replaces the old one instead of contradicting it.

Vodou is for engineers who use several AI tools a day, are tired of each one starting from zero, and want that memory on their own machine. The client side (MCP servers, skills, scripts, the extension) is open source and yours to extend. This post was mined from that memory, then drafted, graded and redaction-scanned by jobs on Vodou’s own scheduler.

See what it would remember for you at vodou.ai.