Back to all posts
ArchitectureReactFSDDXFrontend

The Smeared Component: When Architecture Scatters What Should Stay Together

May 9, 2026
8 min read

I've been writing code since 2003. That's over twenty years of watching patterns come and go — and occasionally making the same mistakes the patterns were supposed to prevent.

My first serious FSD project was one of those mistakes.

Feature-Sliced Design is one of the most popular frontend architectures today. The docs are clear, the community is active, the rules make sense on paper. You read through it and something clicks: pages here, features there, entities in their own layer, shared utilities at the bottom. Clean. Logical. Structured.

So I followed the rules. Meticulously.

And I ended up with a codebase where a single button — one button that validated a form field — lived across ten folders. Its logic was in features/. Its type was in entities/. Its hook was in shared/hooks/. Its analytics call was somewhere in features/analytics/. Its test was buried in __tests__/features/. To understand what that button did, you had to open half the project.

I was frustrated. For a while, I blamed myself — maybe I didn't understand FSD well enough. Maybe I was doing it wrong. It took me some time to realize: the problem wasn't the architecture. The problem was that I had stopped thinking.

The Smeared Component

There's a pattern I've started calling the smeared component. You've probably seen it. You might have built it.

It happens when a single feature — a payment form, an order widget, a settings panel — gets distributed across the project's folder structure "correctly." Each piece goes to its designated layer. Hooks go to hooks/. Types go to types/. API calls go to api/. Validators to validators/. Stories to stories/. Tests to __tests__/.

The result looks organized from the outside. Every folder has the right things in it.

But try to understand the component. Try to fix a bug in it. Try to onboard a new developer onto it. You'll open one file, realize you need another, follow an import, land in a third folder, lose track of the original file, open a fourth — and fifteen minutes later you're not sure you've seen all the pieces yet.

Code is read far more often than it is written. Architecture should optimize for understanding — not just separation.

The smeared component optimizes for separation. It's perfectly layered. And it's nearly impossible to hold in your head.

A Real Example: PaymentMethodSelector

Let me show you what this looks like in practice.

Imagine a PaymentMethodSelector component — the kind you see on checkout pages. It fetches available payment methods from an API, displays them with icons and descriptions, handles selection state, validates the selected method against order constraints, shows a modal for adding a new card, fires analytics events on selection change, and stores the result in global state.

A well-intentioned FSD implementation might distribute it like this:

src/
  features/
    payment-method/
      ui/
        PaymentMethodSelector.tsx     ← main component
        PaymentMethodOption.tsx
        AddPaymentModal.tsx
  entities/
    payment/
      model/
        payment.store.ts              ← Zustand store
        payment.selectors.ts
      api/
        payment.api.ts                ← API calls
      types/
        payment.types.ts              ← types and interfaces
  shared/
    hooks/
      usePaymentValidation.ts         ← validation logic
    analytics/
      payment.analytics.ts            ← analytics events
    lib/
      formatters/
        payment.formatters.ts         ← formatting helpers
  __tests__/
    features/
      PaymentMethodSelector.test.tsx
  stories/
    PaymentMethodSelector.stories.tsx

Eight locations. For one component.

Now imagine the validation logic needs to change — a new payment method has different constraints. You update usePaymentValidation.ts. The type needs updating — payment.types.ts. The API response shape changed — payment.api.ts. The analytics event needs a new field — payment.analytics.ts. Update the test — somewhere in __tests__/features/. Update the story. Six folders. One change.

Every bug fix is an archaeological dig. Every refactor is a leap of faith.

Here's what the same component looks like with colocation:

src/
  features/
    payment-method/
      PaymentMethodSelector.tsx
      PaymentMethodSelector.types.ts
      PaymentMethodSelector.hooks.ts
      PaymentMethodSelector.api.ts
      PaymentMethodSelector.store.ts
      PaymentMethodSelector.analytics.ts
      PaymentMethodSelector.utils.ts
      PaymentMethodSelector.test.tsx
      PaymentMethodSelector.stories.tsx
      ui/
        PaymentMethodOption.tsx
        AddPaymentModal.tsx

Everything that changes together lives together. One folder. One mental unit. One place to look.

"But FSD Doesn't Allow That"

I can already hear the objection: "FSD has rules. Types belong in entities. Hooks belong in shared. You can't just put everything in features."

Here's the thing: FSD is a tool. Not a commandment.

The methodology was designed for the common case — and the common case is a typical CRUD feature with thin logic and shared primitives. For a simple UserAvatar component, fine — put the type in entities, the API call in shared. The overhead is minimal because the component is minimal.

But for a complex, domain-rich feature like PaymentMethodSelector? The cost changes. Every "correct" placement is a navigation tax you pay every time you touch the component.

FSD actually allows adaptation. You can add a core/ layer for truly foundational cross-cutting concerns. You can extend shared/ with domain-specific subfolders. You can colocate everything within a feature when that feature is complex enough to justify it. The methodology explicitly says the structure should serve the project — not the other way around.

I've worked on a project where the team extended FSD's layer model to add a core/ layer for infrastructure-level services: logging, auth tokens, global config. Things that don't belong to any feature but are too specific for shared/. It worked cleanly. No FSD rule was violated — because the rule was never "these are the only layers you can have." The rule was "maintain the import direction and keep dependencies explicit."

Architecture is not what you do by the rules. It's how you solve the problem in front of you — in a way that stays solvable tomorrow.

Why We Keep Doing This

The reason we smear components is usually good intentions. We read the docs. We follow the patterns. We want the project to be "clean." We've been burned by messy codebases before and we never want to go back.

So we apply the architecture top to bottom, folder by folder, layer by layer. And it works. Until the project grows. Until the component gets complex. Until someone new joins and spends three days just finding where things are.

I've worked in legacy codebases where the original authors followed every rule. The folder structure is immaculate. And the cognitive load of working in it is brutal — because everything looks right but nothing reads right. Opening a component means opening a new tab for the hook, a new tab for the type, a new tab for the API call. Your brain is juggling six contexts at once.

I've been that original author too, on my first FSD project. I was so focused on placing files correctly that I stopped asking whether the placement made the code better.

What Architecture Actually Optimizes For

Good architecture optimizes for:

  • Locality — related things are close to each other
  • Discoverability — you can find what you're looking for without a map
  • Changeability — when requirements change, the blast radius is predictable
  • Cognitive simplicity — a developer can hold the relevant context in their head

Less useful to optimize for:

  • Folder aesthetics
  • Layer purity
  • DRY at the cost of everything else

These aren't opposites. A well-applied FSD project can nail all the good ones. But "well-applied" requires judgment, not just rule-following.

The principle I keep coming back to: things that change together should live together. It sounds simple. It's not always easy to apply. But it's the right question to ask before you decide where a file goes.

A Practical Test

Before placing a file in its "architecturally correct" location, ask yourself:

  1. If I need to change this file, what other files will I need to open at the same time?
  2. Are those files nearby — or spread across the project?
  3. If a new developer needed to understand this feature, how many folders would they need to open?

If the answers make you uncomfortable, you're probably optimizing for layer purity instead of developer experience. Reconsider the placement.

This isn't "ignore architecture." It's the opposite — it's using the architecture deliberately. FSD, done thoughtfully, is excellent. Applied mechanically — rule by rule, folder by folder, without judgment — it produces smeared components and frustrated developers.

I've worked on both kinds of projects. The difference wasn't the architecture they used. It was the thinking behind it.

Found this helpful?

Let's discuss your project needs.

Get in touch