React components are often split too early or too late. Learn a practical mental model for component architecture that prioritizes performance and readability.
I remember staring at a 400-line Dashboard.tsx file at 2:00 AM, trying to figure out why a simple toggle caused the entire page to stutter. We had all the logic, styles, and data fetching in one giant component. It was a classic case of ignoring the importance of well-defined boundaries.
When you're starting out, the advice is always "keep your components small." But that’s incomplete. If you break everything into tiny, two-line components, you end up with a "prop-drilling" nightmare that is harder to debug than the original monolith. You need a strategy that considers how React actually works under the hood.
When you define where a component ends and another begins, you’re essentially drawing a line in the sand for React’s reconciliation process. If a component re-renders, everything inside it—all its children—will attempt to re-render by default. This is why understanding React state snapshots is so critical; if you don't keep your state local to the components that actually need it, you’ll trigger unnecessary updates across your entire tree.
I used to think that "reusability" was the only reason to split a component. If I needed a button in two places, I’d pull it out. But that’s a secondary concern. The primary concern is performance and cognitive load.
We once tried to optimize a heavy data table by wrapping every row in a memo component. It actually made the app slower because the overhead of the memoization checks outweighed the cost of the re-render. We had created the wrong boundaries.
Instead of arbitrary rules, I use these three heuristics to decide if a piece of UI should be its own component:
A few years ago, I worked on a project where we tried to create a "universal" form component. It had props for every conceivable input type: text, checkbox, select, date, and radio. It became a 600-line monster with so many if/else statements that nobody wanted to touch it.
We had prioritized "DRY" (Don't Repeat Yourself) over "DAMP" (Descriptive and Meaningful Phrases). We should have just written three distinct, simple form components instead of one complex, generic one.
When you look at your component architecture, ask yourself: "If this inner part changes, does the parent need to know?"
If the answer is no, move that part into its own component. This is the secret to keeping your react performance in check. By lifting state out of the parent, you minimize the surface area of your re-renders.
Here’s a quick mental checklist I run through before creating a new file:
If it fails these, keep it in the parent file. Don't split just to satisfy a "clean code" aesthetic.
How many lines of code should a component be? There is no magic number. I’ve seen 10-line components that were confusing and 150-line components that were perfectly readable. Focus on "Single Responsibility" rather than line counts.
When should I use memo?
Only when you have a performance bottleneck. Profile your app with the React DevTools first. If you aren't seeing a performance hit, don't waste time with memo. It adds complexity that you might not need.
Does splitting components hurt bundle size? Generally, no. Modern bundlers are excellent at tree-shaking. Splitting your components actually makes it easier for the compiler to optimize your code.
I still struggle with this. Sometimes I split too early, and sometimes I let a file grow until it’s a mess. The key is to treat your component boundaries as living, breathing things. If a component feels like it’s doing too much, break it apart. If you find yourself passing ten props down through four layers of components, you’ve likely drawn your boundaries in the wrong place.
Next time you’re refactoring, try moving the state down rather than moving the logic out. You might find that your code becomes much easier to reason about without adding a single new file.
React component architecture thrives on colocation. Learn why grouping logic, state, and UI by feature beats file-type organization for scalable Next.js apps.