Introduction
Many Salesforce teams still ask whether they should build in Lightning Web Components (LWC) or Aura. The short answer is not balanced: for new development, Salesforce recommends LWC as the default programming model. Aura remains important mostly because older implementations still exist, some platform capabilities still depend on Aura, and many enterprise orgs need a staged migration rather than a full rewrite.
The real architectural decision is usually not "Which framework is better in theory?" It is "How do we move toward LWC without breaking a stable app, losing unsupported features, or creating migration churn that the business did not ask for?"
Official Salesforce guidance
Salesforce's own LWC documentation says to always choose Lightning Web Components unless you need a feature that is not supported. That is the cleanest rule in the entire comparison and it should drive architecture reviews, code review comments, and migration prioritization.
Salesforce also documents two important interoperability boundaries that matter in real projects:
- Aura can contain LWC. This is why incremental migration is feasible.
- LWC cannot contain Aura. Once an LWC is the outer shell of a subtree, everything below it must also be LWC.
That one-way composition model explains why teams often keep an Aura wrapper temporarily while they migrate the inner business UI to LWC.
Architecture differences
LWC is closer to standard web development. Its component model, lifecycle, events, and templating map more naturally to modern browser concepts, which usually makes the code easier to reason about for developers coming from JavaScript frameworks or web platform APIs.
Aura is older and more framework-driven. It uses its own event model, attribute syntax, component lifecycle, and controller/helper patterns. Aura still works, but it generally carries more framework ceremony and more legacy cognitive load.
LWC strengths
- Better default for new development.
- Cleaner JavaScript module structure.
- Closer alignment with modern web standards.
- Stronger long-term maintainability for most teams.
Aura strengths
- Useful for legacy app continuity.
- Can host LWCs during phased migration.
- Still needed for some unsupported interfaces or base components.
- May reduce short-term risk when a large mature app is already stable.
LWC tradeoffs
- Cannot contain Aura components.
- Some Aura-era capabilities still require wrappers or alternate design.
- Styling is more intentionally bounded, which is good for isolation but less flexible for ad hoc overrides.
Aura tradeoffs
- Not Salesforce's preferred model for new work.
- More framework-specific code and patterns.
- Harder to justify when a fresh build could simply start in LWC.
Comparison table
The table below summarizes the differences that most often affect architecture decisions.
| Area | LWC | Aura | Why it matters |
|---|---|---|---|
| Platform recommendation | Preferred for new development. | Legacy but still supported. | Architecture reviews should treat LWC as the default path. |
| Programming model | Modern JavaScript classes, modules, and reactive templates. | Framework-specific markup plus controller and helper files. | LWC is usually easier to onboard and maintain. |
| Composition | Can contain only LWC children. | Can contain Aura and LWC children. | Aura is useful as a migration shell around new LWCs. |
| Styling model | More isolated and standards-aligned, with stronger boundaries around custom styling. | More permissive for legacy custom styling patterns. | LWC reduces accidental style leakage but sometimes forces cleaner design decisions. |
| Base component coverage | Broad coverage, but not every Aura base component has an exact LWC equivalent. | Still includes some components or capabilities without an LWC replacement. | This is one of the few legitimate reasons to stay in Aura for a given surface. |
| Operational future | Better strategic fit for Salesforce UI going forward. | Primarily important for coexistence and migration. | New UI debt is less likely when teams standardize on LWC now. |
Salesforce's current component mapping documentation is also worth checking before a migration spike. Some replacements are direct, but some have subtle API differences. For example, `lightning:card`, `lightning:datatable`, `lightning:formattedNumber`, and `lightning:recordEditForm` do not always migrate as a one-line rename.
Side-by-side code example
The examples below implement the same simple record-page behavior in both frameworks: load an Account by `recordId` and render a small summary card. The goal is not to show every syntax detail. It is to make the architectural difference obvious.
Apex controller used by both components
public with sharing class AccountSummaryController {
@AuraEnabled(cacheable=true)
public static Account getAccount(Id accountId) {
return [
SELECT Id, Name, Industry, Phone
FROM Account
WHERE Id = :accountId
LIMIT 1
];
}
}
Aura version
<aura:component controller="AccountSummaryController"
implements="flexipage:availableForRecordHome,force:hasRecordId">
<aura:attribute name="account" type="Account" />
<aura:attribute name="errorMessage" type="String" />
<aura:handler name="init" value="{!this}" action="{!c.doInit}" />
<lightning:card title="Account Summary">
<aura:if isTrue="{!not(empty(v.account))}">
<div class="slds-p-around_medium">
<p><strong>Name:</strong> {!v.account.Name}</p>
<p><strong>Industry:</strong> {!v.account.Industry}</p>
<p><strong>Phone:</strong> {!v.account.Phone}</p>
</div>
<aura:set attribute="else">
<div class="slds-p-around_medium slds-text-color_error">
{!v.errorMessage}
</div>
</aura:set>
</aura:if>
</lightning:card>
</aura:component>
({
doInit: function(component) {
const action = component.get("c.getAccount");
action.setParams({ accountId: component.get("v.recordId") });
action.setCallback(this, function(response) {
if (response.getState() === "SUCCESS") {
component.set("v.account", response.getReturnValue());
} else {
component.set("v.errorMessage", "Unable to load account.");
}
});
$A.enqueueAction(action);
}
})
LWC version
import { LightningElement, api, wire } from 'lwc';
import getAccount from '@salesforce/apex/AccountSummaryController.getAccount';
export default class AccountSummary extends LightningElement {
@api recordId;
@wire(getAccount, { accountId: '$recordId' })
account;
get errorMessage() {
return this.account.error ? 'Unable to load account.' : '';
}
}
<template>
<lightning-card title="Account Summary">
<template lwc:if={account.data}>
<div class="slds-p-around_medium">
<p><strong>Name:</strong> {account.data.Name}</p>
<p><strong>Industry:</strong> {account.data.Industry}</p>
<p><strong>Phone:</strong> {account.data.Phone}</p>
</div>
</template>
<template lwc:elseif={account.error}>
<div class="slds-p-around_medium slds-text-color_error">
{errorMessage}
</div>
</template>
</lightning-card>
</template>
The LWC version is not just shorter. The reactive data flow is more direct, the module structure is clearer, and the framework-specific ceremony is lower. That is why LWC usually wins for new component work.
Aura and LWC interoperability
Coexistence is where many enterprise orgs live today. A large app may still be Aura at the page or container level, while new business widgets are implemented in LWC underneath. Salesforce explicitly supports that direction.
A common pattern is to leave a thin Aura wrapper in place while the real UI is migrated to an LWC child. That gives you a lower-risk path than rewriting the host and every nested dependency at once.
Minimal Aura wrapper around an LWC child
<aura:component implements="flexipage:availableForRecordHome,force:hasRecordId">
<aura:attribute name="recordId" type="Id" />
<c:accountSummary recordId="{!v.recordId}" />
</aura:component>
This pattern is especially useful when:
- The surrounding page structure is still Aura.
- You are replacing one nested component at a time.
- You need the smallest possible Aura surface while moving business logic and rendering into LWC.
The same wrapper strategy also applies when you depend on an Aura-only experience or interface. Keep the Aura layer tiny and keep the real business UI in LWC wherever possible.
When Aura still makes sense
Aura is no longer the default, but there are still valid reasons to keep or use it in a targeted way.
Keep Aura when the app is stable
- The business value of migration is low right now.
- The component is not causing performance, security, or maintenance pain.
- A rewrite would create more regression risk than benefit in the current release window.
Use Aura when LWC lacks a needed capability
- You rely on an Aura-only base component or interface.
- You have confirmed there is no clean LWC equivalent in current Salesforce docs.
- You intentionally keep the Aura surface as small as possible.
Examples from current docs
- `lightning:overlayLibrary` has no direct LWC component equivalent.
- Some Aura base components such as `lightning:path`, `lightning:listView`, or `lightning:unsavedChanges` still require extra evaluation.
- Some direct component mappings exist but behavior differs enough that migration still needs testing.
What does not justify Aura
- "The old project used Aura."
- "We know Aura better."
- "It might be easier to copy an old component."
- "We have not checked whether LWC already supports it."
Migration strategy
Aura-to-LWC migration works best as a sequence of architectural decisions, not as a blanket conversion exercise.
- Inventory the Aura estate. Separate stable low-value legacy components from high-value components that actively need enhancement.
- Check Salesforce's component mapping docs first. Some migrations are direct, some require API adjustments, and some still need Aura.
- Migrate leaf components before host containers. This lets Aura wrappers host new LWC children while the page keeps working.
- Replace business logic with shared Apex or services where practical. That reduces rewrite duplication.
- Remove Aura only after the last genuine dependency is gone. Do not rewrite wrappers before the underlying capability is ready.
For many teams, the best migration target is not "all Aura." It is "no new Aura unless documented necessity." That rule alone usually improves the codebase over time.
Best practices
- Default to LWC for all new custom UI. Make exception requests explicit.
- Use Aura wrappers strategically. They are a bridge, not the destination.
- Re-check official docs before assuming a gap still exists. Base component coverage and migration guidance evolve.
- Test mapped components carefully. Some Aura-to-LWC replacements look similar but differ in attributes, slots, or event behavior.
- Prefer shared Apex and reusable services. It makes partial migration less painful.
- Do not launch a big-bang rewrite without business pressure. Incremental migration is safer and usually easier to support.
- Keep styling cleaner in LWC. Favor SLDS and supported styling hooks instead of relying on broad CSS overrides.
- Review composition boundaries early. Remember that an LWC subtree cannot drop an Aura child into the middle later.
Recommendation
If you want one decision rule, use this: build in LWC unless you can point to a real Aura-only dependency. If a legacy app is already in Aura, migrate incrementally by moving inner functionality to LWC and keeping only a thin Aura shell where necessary.
For architects, the goal is not ideological purity. It is lowering future UI debt. For developers, the goal is to write the smallest amount of framework-specific code that still satisfies the business requirement. In 2026, that usually means LWC first, Aura only by exception.