Not verified hands-on. This article was written from primary documentation rather than reproduced in an org, so it carries no tested-release badge. If you run it and something differs, say so in the comments and it gets tested and corrected.

The short version: migrate leaves first and work upwards, because an LWC can contain an Aura component only by not containing it. Aura can hold an LWC child. An LWC cannot hold an Aura child. Get that one rule wrong and you will convert a container early, discover its three children cannot go inside it, and have to convert all four in one commit.

The interop rules, in the order they will bite you

  • Aura can contain LWC. LWC cannot contain Aura. This is the constraint that dictates the whole sequence.
  • Aura markup can listen to an LWC’s CustomEvent with an on attribute pointing at a controller action, the same as any DOM event.
  • An LWC cannot subscribe to an Aura application event. There is no bridge. Replace the application event with a Lightning Message Service channel, which both sides speak.
  • Aura can call an LWC’s @api method through component.find('childLwc').doTheThing(). A useful crutch mid-migration.
  • Apex classes, labels and static resources are shared. Nothing to migrate there, which is why the Apex layer should not be part of this project.

The order that does not strand you

Step one: map the tree. Grep your Aura bundles for <c: references and build a parent-child graph. You want the roots (usually on a Flexipage or a quick action), and the leaves, which contain nothing. Everything between is ordered by that graph.

Step two: kill the application events first. Before converting anything, replace every <aura:event type="APPLICATION"> with a message channel, still inside Aura. It is a small, testable change while both ends are Aura. Do it after you have converted one side and you are debugging a security model, a framework boundary, and a new API at once.

Step three: leaves, in batches. Convert a leaf, drop it into its unchanged Aura parent, deploy, test. The parent’s markup barely changes: the tag name and the attribute casing (recordId becomes record-id), and the event handlers. This is the phase where you get real value early, because leaf components are usually the ones with the performance problems.

Step four: containers, last. By the time you convert a container, all its children are already LWC, so the conversion is just the container.

Do not port one-for-one

Aura’s boilerplate encouraged small components: a bundle for the markup, another for the modal, a third for the row. LWC’s slotting and template directives mean two Aura bundles are often one LWC. Migration is the only time anybody will give you licence to delete components, so take it.

Drop the Aura habits with no equivalent too. aura:attribute becomes a property with @api. Manual $A.getCallback wrapping becomes nothing at all. component.set and component.get become assignment and reading.

What you cannot move yet

The blocker is almost never the component code, it is the surface it renders on. An LWC is only usable where its js-meta.xml can declare a matching target, and a handful of surfaces have never had one. Check the current target list in the Lightning Web Components Developer Guide before you plan a conversion, because this list has shrunk steadily and anything you remember from three years ago is probably wrong now.

When you do hit a surface with no LWC target, the workaround is a thin Aura wrapper: an Aura bundle that implements the required interface and contains nothing but your LWC. It is ugly, it costs one extra bundle, and it means the logic is still written once. Do that rather than keeping a real Aura implementation alive.

The other genuine blocker is a managed package component you do not own. It stays Aura, and anything containing it stays Aura with it. Find those early. They pin whole branches of your tree.

How to know you are finished

Not when the last bundle is deleted. When no Aura component sits between a Flexipage and an LWC, because that wrapper is where the performance cost lives. An org with one remaining Aura component that wraps nothing important is in a fine state. An org with thirty LWCs each inside its own Aura shell has done the work and kept the cost.

Frequently asked

Can an Aura component and an LWC share state? Through the Lightning Data Service cache, yes, automatically for record data. For anything else, a message channel.

Do I need to migrate at all? Aura still runs. The argument for moving is bundle size, the standard JavaScript skill set, and that new platform capability arrives as LWC first. Enough for me, but not an emergency.

How do I test the boundary during migration? In a browser, on a real page. Jest runs your LWC in isolation and knows nothing about the Aura parent it will actually live inside.