FrontalCode All articles
Frontend Frameworks

Wisdom That Isn't: 7 Frontend Habits That Sound Smart and Cost You Real Money

FrontalCode
Wisdom That Isn't: 7 Frontend Habits That Sound Smart and Cost You Real Money

Engineering culture runs on transmitted wisdom. Senior dev says it, junior dev writes it down, two years later it's in your team's style guide and nobody remembers where it came from. The problem is that some of this "wisdom" was always wrong, and the rest aged badly. Here are seven habits that keep showing up in code reviews dressed as best practices — and what they're actually costing you.

1. Abstracting Components Before You Have Two of Them

The pattern: someone builds a Button and immediately adds a variant, size, leadingIcon, and asChild prop "for flexibility." Three months later, the component has 14 props, a render function with nested ternaries, and a docs page longer than the implementation.

Why it spreads: abstraction feels like foresight, and nobody gets criticized for being "too prepared."

The cost: every abstraction leaks. Premature ones leak the fastest, because you guessed wrong about what varies. Now every UI change means unwinding a prop API instead of editing markup.

Do instead: wait for the third instance. Duplication is cheap to delete; the wrong abstraction is expensive to remove.

2. Memoizing Everything That Moves

[useMemo](https://en.wikipedia.org/wiki/React_(software)) on every computed value. useCallback on every handler. React.memo on every component. It reads like performance consciousness, but most of it guards against re-renders that were never expensive in the first place.

The cost: memoization adds dependency arrays that go stale, closures that capture the wrong values, and cognitive overhead for every future reader. You've traded obvious code for slightly-less-re-rendering code.

Do instead: profile first. Add memoization where the profiler shows actual re-render pain, and leave the rest readable.

3. Code-Splitting Into Confetti

Dynamic imports everywhere, one route per chunk, components lazy-loaded two levels deep. The bundle analyzer looks beautiful. The app feels like it's loading a surprise box.

The cost: each split adds a waterfall — fetch the chunk, parse it, hydrate it. Over-splitting means users sit through spinner chains while 4KB chunks fetch sequentially. You optimized kilobytes and sacrificed perceived speed.

Do instead: split at route boundaries and genuinely heavy features (charts, editors, maps). Measure real user metrics, not the bundle graph.

4. Tests That Mock the World

The unit test suite is green because every dependency is mocked: the API, the router, the store, the date library, sometimes the component under test's own children. Congratulations — you've written a test that verifies your mocks agree with each other.

The cost: these tests pass while production breaks. Worse, they pin the implementation, so every refactor breaks 40 tests that were never testing behavior.

Do instead: mock the network boundary (with something like MSW) and test user-visible behavior. Let the internals be real.

5. TypeScript Strictness as Performance Theater

Someone adds generics with four type parameters, conditional types, and template literal types to parse a config object — and the runtime code is six lines. The type checking takes longer than the code executes.

The cost: unreadable type gymnastics that only the author can modify, and compile times that make CI grumpy.

Do instead: the type system should describe your program, not replace it. If a runtime check is simpler, write the runtime check.

6. The God Utility Folder

utils/ starts innocently: formatDate.ts, debounce.ts. Eighteen months later it's a junk drawer with 120 files, circular imports, and a helpers.ts that imports everything else.

Why it spreads: "utils" has no domain, so nobody feels responsible for its architecture.

Do instead: organize by feature or domain. If a helper is only used by one feature, it lives with that feature. Shared utilities need a maintainer, even if it's just a CODEOWNERS entry.

7. Treating Every Code Review Comment as Mandatory

The pattern: reviews accumulate nitpicks — rename this variable, use a for-loop, extract this function — and the author churns three extra days satisfying preferences disguised as standards.

The cost: shipping velocity bleeds out one comment at a time, and authors learn to dread the review process rather than value it.

Do instead: distinguish blocking issues (bugs, security, real maintainability) from preferences. Preferences go in the style guide or get automated with a linter, not litigated per PR.

The Common Thread

Notice what all seven have in common: each one optimizes for looking rigorous rather than being effective. Real best practices survive contact with measurement. So audit your team's habits against actual outcomes — bundle metrics, test failure rates, PR cycle times. The wisdom that survives that audit is worth keeping. The rest is just tradition with a linter.

All Articles

Related Articles

Inheriting a Codebase Nobody Explains? Here's Your First Week Playbook

Inheriting a Codebase Nobody Explains? Here's Your First Week Playbook

Your Test Suite Is a Liability. Here's How to Make It an Asset Again.

Your Test Suite Is a Liability. Here's How to Make It an Asset Again.

Stop Treating Forms Like an Afterthought — They're the Core of Your App's UX

Stop Treating Forms Like an Afterthought — They're the Core of Your App's UX