Most Lightning Web Components I inherit were built for one screen and never touched again until the second screen needed almost the same thing. Someone copies the folder, renames it, tweaks a field reference, and ships it. Six months later there are four components doing the same job with four slightly different bugs. Nobody planned that. It’s just what happens when reuse isn’t a design decision from the start.
Building for reuse doesn’t mean over-engineering a component before you know if it’ll ever be reused. It means making a small number of choices early that keep the door open, so the second and third use case are a configuration change instead of a fork.
What reuse actually means here
Reuse in LWC isn’t about component count. A component that only ever lives on one page can still be badly designed, and a component copied into five places can still be tightly coupled if every copy needed manual edits to work. The real test is simpler: can someone drop this component onto a different object, a different record type, or a different flow without opening the JavaScript file?
If the answer is no, it’s not reusable. It’s duplicable. Those are different things, and the difference shows up in maintenance cost about a year later, usually right when nobody remembers why there are four almost-identical versions floating around.
The habits that quietly block reuse
Hardcoded object and field references
A component that queries Case fields directly in its JavaScript, or references a specific record type by hardcoded Id, works exactly once. The next team that wants the same UI on Work_Order__c can’t reuse it. They fork it. Now there are two components with the same template and diverging logic, and the next bug fix has to land twice.
The fix isn’t complicated. Pass the object API name, field list, or record type as a public property (@api) instead of writing it into the component. Wire adapters like getRecord and getFieldValue already support dynamic field references built from those properties. Most teams know this. Fewer actually do it under a deadline, which is exactly when the shortcut gets taken and never revisited.
Business logic sitting where the template can see it
When validation rules, formatting logic, and layout are all written in the same JavaScript controller, changing one thing risks breaking another. A component that decides both “how do I calculate this discount” and “how do I render this card” is doing two jobs badly instead of one job well. Split the calculation into its own module, imported where needed, and let the component’s controller focus on wiring data to the template. That calculation module can then get reused in a completely different component, or in Apex-adjacent client logic, without dragging the UI along with it.
One component trying to handle every variant
The opposite failure is just as common: a single component with a dozen boolean @api flags controlling which sections render, because someone kept bolting new requirements onto the original instead of composing smaller pieces. I’ve seen components with eleven optional properties where changing one variant meant testing all eleven combinations to make sure nothing else broke. That’s not reuse. That’s a maintenance trap wearing a reuse costume.
Patterns that actually hold up
Public properties over internal fetches
Where possible, have the parent pass data down rather than having every child component independently wire its own Apex call or record query for the same underlying data. It’s tempting to make each component self-sufficient, but five components each running their own query for the same account fields multiplies API calls and cache misses for no real benefit. A parent that fetches once and distributes via properties is both faster and easier to reason about.
Events up, properties down
Keep the data flow direction consistent. Parent to child is properties. Child to parent is custom events. A component that reaches up into its parent’s state, or that manipulates a shared record outside its own boundary, breaks the moment it gets moved into a different parent context. If a component only ever communicates through properties in and events out, it can be dropped into an entirely different page without anyone reading its source code first.
Composition over configuration flags
Instead of one component with many optional sections, build a few small, focused components and compose them in the parent template. A “record header,” a “related list,” and an “action bar” as three separate components are each easier to test, easier to reuse individually, and easier to reason about than one twelve-property mega-component trying to be all three depending on which flags are set.
A working example
On a contact-centre implementation I worked on, the original requirement was a compact case summary card for the agent console. Six weeks later, the same card was wanted on the account page, then on a custom Utility Bar panel, then embedded in a Flow screen. Because the first version had already pulled its object, its field set, and its “compact vs full” display mode out into public properties, none of those three later requests needed new component code. They needed configuration in the Lightning App Builder and, in the Flow case, a handful of input variables mapped to existing properties. What would have been three separate build tickets became one afternoon of admin configuration.
That’s the actual payoff of designing for reuse. It’s not a cleaner GitHub repo. It’s fewer tickets landing on a developer’s desk for something an admin could configure themselves, if the component had been built to allow it.
When it’s genuinely not worth it
Not every component needs this treatment. A one-off internal tool used by two people, or a component built for a feature that’s explicitly temporary, doesn’t need public properties and composed sub-components designed for a future that may never arrive. Over-abstracting a throwaway component wastes time that should go toward the components that actually sit on multiple pages: record headers, related list variants, quick-action panels, anything that shows up in more than one Lightning page across the org.
The judgment call is knowing which is which before you start typing, not after the third fork shows up in a pull request.
A quick check before you build
- Does this component reference a specific object, field, or record type directly in the JavaScript? If yes, can it be a public property instead?
- Is there business logic mixed into the same file as the rendering logic? If yes, can it move to its own module?
- Does the component need to reach outside its own boundary to work, touching parent state or global variables? If yes, that’s a sign it won’t survive being moved.
- Would a second use case require someone to open and edit the JavaScript, or just change configuration? The second answer is the one you want.
None of this takes much longer to build the first time. It just means resisting the urge to hardcode the one thing you know for certain right now, because the certainty rarely lasts past the next sprint.
Leave a Reply