The Fergy Stack

Linux at the desk, adventures on the table.

Performance Starts Before the Profiler

The most satisfying performance wins I’ve ever shipped didn’t come from heroic late-night profiling sessions with fifteen tabs open and a sinking feeling in my stomach.

They happened earlier. Quietly. Almost boringly.

They happened when someone paused during implementation and asked, “Where does this state actually belong?” Or when we decided to keep a data flow simple instead of “flexible.” Or when we refused to introduce a memoization maze we knew we wouldn’t understand two sprints from now.

Here’s the point I’ve come to believe: performance work is best when it’s treated like architecture, not emergency response. Most teams think “performance” begins when something is slow. In reality, it starts when you decide how your UI is shaped—what owns state, how components communicate, and what changes cause re-renders.

The invisible tax: unnecessary work

Not everything needs optimization. But everything benefits from intention.

Because the browser is not the only thing doing work. Your teammates are doing work too—debugging, reading, extending, and trying to answer “why is this rerendering?” without developing a second career in interpretive dance.

A feature can be “fast enough” and still be expensive. Expensive in mental load. Expensive in incident risk. Expensive in onboarding time. And those costs compound in the exact same way performance issues do: gradually, then suddenly.

If you want a litmus test that isn’t about frame rates, ask this:

Does this scale mentally for the next person who touches it?

That question is performance engineering for teams.

State boundaries are performance boundaries

Most UI performance issues in React are not “React being slow.” They’re “we made change too contagious.”

When state is too high, everything re-renders. When state is too low, you end up threading props like you’re making friendship bracelets at summer camp. When state is the wrong shape (one mega object that changes constantly), you’re basically attaching a “rerender everything” siren to every update.

State boundaries are about deciding what should change together—and what should never have to care.

A practical heuristic: split boundaries by volatility.

  • Highly volatile state (typing in an input, hover states, local toggles) should live close to where it’s used.
  • Shared state should be shared for a reason, not because it was “easier” on day one.
  • Derived state should be derived, not duplicated.

When you get this right, you don’t just prevent slowdowns. You prevent the class of bugs where UI shows “almost correct” data because two bits of state drifted out of sync.

Memoization: the sharp tool everyone overuses

Memoization (useMemo, useCallback, React.memo) is like a high-end kitchen knife: incredibly useful, and also a great way to cut yourself while trying to open a bag of chips.

Used intentionally, it can reduce wasted work in expensive renders or stabilize references for child components. Used as a blanket policy, it creates a codebase that looks optimized but is actually harder to reason about—and sometimes slower due to the overhead and missed opportunities for React to do its job.

The contrarian truth: most memoization is compensation for unclear boundaries.

If you have to memoize everything, that’s usually a signal that state is in the wrong place, props are too unstable, or a component is doing too much. Fix the structure first. Then memoize the hotspot you can measure.

Or put differently: don’t turn your component tree into Optimus Prime just because you saw one render you didn’t like. Transformation is cool. Unnecessary transformation is how you end up with a robot that can’t fit through the door.

Data flow: performance is predictability

When data flow is clean, performance improvements are easy because the system is legible.

When data flow is messy, performance work becomes archaeology.

The biggest early wins come from boring decisions:

  • Keep data flow one-directional and explicit.
  • Prefer composition over “global reach” patterns.
  • Avoid hidden subscriptions and implicit coupling.
  • Normalize server state vs. UI state (don’t blend them into one stew).

A simple example: if a list page fetches server data and a detail drawer edits something, decide early who owns the “truth.” Does the drawer patch the cache? Does it bubble up changes? Does it optimistically update? Any of those can be fine—what kills teams is when the answer is “a little bit of each depending on the day.”

Performance isn’t just speed. It’s predictability under change.

The part everyone skips: designing for Tuesday

Most performance talk is about big-O, rendering waterfalls, and graphs. Useful, sure. But the real world is Tuesday at 4:57pm:

  • A PM wants “just one more filter.”
  • A designer updates spacing tokens.
  • A teammate touches a file they’ve never seen.
  • Something rerenders weirdly and no one knows why.

If your architecture makes those moments easy, you’re doing performance work—even if Lighthouse is already green.

This is why “preventative performance” matters. It reduces the number of places change can do damage.

Or, in G.I. Joe terms: knowing where your state lives is half the battle. The other half is not hiding the battle inside twelve custom hooks named useMagic.

If you try this next week

A few practical moves that pay off early:

  • Draw your state boundaries before coding the UI. Ask: what changes together, what shouldn’t?
  • Keep volatile UI state local by default. Lift it only when there’s a clear consumer.
  • Measure before memoizing. If you can’t point to wasted work, don’t “optimize vibes.”
  • Watch for “contagious props”: objects/functions recreated every render that cascade through the tree.
  • Separate server state from UI state. Let each have its own lifecycle and tooling.
  • Prefer simpler data flow over clever indirection. Future-you will file a thank-you ticket.

Performance is a feature, but it’s also a culture. It’s the habit of designing systems that do less unnecessary work—on the CPU and in people’s heads.

If you’re dealing with a React codebase that feels sluggish or just weirdly hard to change, I’m happy to take a look. Send me your stack, team size, what’s painful, and your timeline.

Want to talk this through?

If you’re dealing with messy React boundaries, TypeScript drift, or a deployment pipeline that hates you back, let’s chat.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *