Why your vibe coded app crashes with a numbered error
I pushed a build to production, opened it, and got a white screen. The console had no stack trace, no component name, no file, no line. It had one sentence telling me the error had been minified, and a number.
If your vibe coded app crashes on render and all you get is a numbered error, the number is not the bug. It is a lookup key. Go decode it first, before you touch a single line, because the number tells you what class of rule you broke, and the class is the thing worth knowing. The crash itself will surface in whichever component happened to render first, which is almost never the component that caused it. That gap between where it throws and where it lives is the entire reason this bug eats an evening.
what does minified React error 310 actually mean?
It means "rendered more hooks than during the previous render," and it is a rules-of-hooks violation rather than a broken component.
React ships numbered errors in production builds on purpose. The full message text costs bytes over the wire, so the production build sends an index and hosts the actual sentence on a decoder page instead. That is a reasonable trade for everyone except the person staring at a white screen at the exact moment they need words.
So the first move is not debugging, it is translation. Take the number to the official decoder and get your sentence back. Mine came back useful in a way I did not expect: it did not name a component, it named a rule. Hooks have to be called in the same order and the same quantity on every single render, and something in my app was calling a different number of them the second time around than it had the first. That is a very different starting point than "this component is broken." It tells you to go looking for a shape, not a location.
the shortcut my generator took everywhere
Here is where the evening went sideways, and where the real receipt is.
My first instinct was to find the component in the crash and fix it. The problem is that the component in the crash was just the first one to render. It was not doing anything wrong. It was standing where the damage landed. I bisected for a while, commenting out sections and redeploying, which is slow (production builds are slow) and worse than slow, it is misleading, because every time I removed one suspect the crash relocated to a different innocent component and I got to feel like I was making progress while learning nothing.
What ended it was going and looking at the icons. Every icon in the project had been declared at module scope as a finished element. Not a function that returns an icon, an icon. A constant, evaluated exactly once when the file loaded, then reused in several different spots in the tree.
It reads as clean code. It looks like the DRY thing to do, and the generator wrote it that way consistently, which is exactly why it survived review: it was not one weird line I would have squinted at, it was the house style of the file. That is the part most vibe coders miss on their first pass through generated code. You are scanning for something that looks wrong, and a shortcut applied uniformly does not look wrong. It looks like a convention.
The distinction that matters is small and easy to read past. A module-scope constant is evaluated once, at import. A factory function is evaluated every time something calls it, inside the render that asked for it, where React can account for the work. Those two things look nearly identical at the call site and they are not the same object at all.
how do I fix react error 310?
You convert the shared statics into factory functions so each one gets evaluated inside its own render, and then you check whether the same shortcut exists anywhere else.
The mechanical fix was small and boring. Every icon went from a constant holding an element to a function returning one, so nothing was evaluated at module load and shared across the tree anymore. Each call site got its own fresh element, produced inside the render that asked for it. Once the work belonged to the render that requested it instead of to a constant declared far away at import time, the hook accounting stopped drifting between renders and the white screen went away.
The part worth copying is not the edit though. It is that I did not make the edit until I knew how many places had the pattern. The answer was all of them, and that changed the job from "fix this icon" to "convert this file," which took one pass instead of a night of bisecting.
why the fix is never the line that threw
This is diagnostic-before-fix, and a generated codebase is the environment that punishes skipping it hardest.
The tempting move with a crash is to go to the crash. The stack points somewhere, you go there, you patch it, the crash stops. That works when a human wrote the bug, because humans write bugs one at a time, in one place, usually while tired. It does not work when a generator wrote the bug, because a generator does not get tired and does not vary. It applies its shortcut with total consistency across every file you asked it to touch. So the crash you can see is a sample, not the population.
If I had patched the icon that happened to be in the stack trace, the app would have come back up, and I would have shipped the same latent bug in every other file, waiting for a different render order to expose it. That is the outcome that scares me more than the white screen did, because a white screen at least tells you it is there.
The rule I keep is to spend the first move counting instead of fixing: find every site that shares the shape before changing any of them. In hand-written code that discipline is good hygiene. In vibe coding it is the difference between one pass and a month of whack-a-mole, because fixing one bug is how you break another when the bug was never singular to begin with.
what tends to break with this
The first failure mode is trusting the stack trace's location in a minified build. It is telling you where the error surfaced, not where it originated, and with a hook-ordering violation those two are almost guaranteed to be different components. Decode the number, understand the rule, then go looking for violations of that rule. Do not start at the crash site.
The second is that it only shows up in production, which makes it feel like the deploy is cursed. Development builds keep the full error text and behave differently around a lot of this, so the environment where you decided the code was fine is the one environment that will not show you this class of problem. This is the same root as a vibe coded build that works in preview and breaks on deploy: you validated in the friendly environment and shipped to the real one.
The third is the one that actually costs money later. Once you know the pattern, the temptation is to fix the file you are in and move on, because the app is up and the pressure is off. But the generator has that shortcut in its habits, and the next feature you ask for will have it again. Either the pattern goes in the spec as a named thing not to do, or you will meet it a third time in a file you have not written yet.
questions that keep coming up
Why does it work in dev and crash in production? Different build, different error handling, and often a different render order. Dev keeps full messages and tolerates more. Production is the only place you will see the numbered version, so production is the only place this particular symptom exists.
Can I just turn the minification off to debug it? You can run a development build locally and get the full message, and that is worth doing to read the sentence. Do not ship a development build to production to keep the error readable. Decode the number instead, it takes ten seconds and costs nothing.
How do I know if my AI wrote this same bug somewhere else? Search for the shape, not the symptom. In this case the shape was a module-scope constant holding an element. Grep the pattern across the project. If a generator wrote it once it very likely wrote it everywhere it had the same job to do, and that search is faster than any bisect.
If you are a vibe coder shipping a custom app past the prototype stage and you want a second set of eyes on what your generator repeated across the codebase, /work-with-us. Custom app work starts by reading the patterns rather than the crashes, because the crash is one sample of the thing and the pattern is the thing.
The number was never the bug. It was just the only thing production was willing to say out loud.
// part of the custom apps topic
// grab the free starter kit that makes your AI stop forgetting and stop guessing: get it →
// building with AI? the field manual has the structured lessons.
// hitting this on a real build? this is what I fix →