From my understanding, there is no perfect solution to this issue. Whatever fix you decide to implement will have its drawbacks.
I think there may also be 2 other techniques that you can use which are ESI (Edge Side Includes) or hole punching.
Although I don't have any experience or knowledge about these, so if these are valid techniques for this purpose.
I'll let someone else with a better understanding of them mention explain them.
If you want the nav to always show the correct active state and don't mind taking a small performance hit, then disable caching on the navigation block and try to keep it as close to the root block as possible.
If you want maximum performance, then disable server-side rendering and hope the visitor has JS enabled.
In core Magento, there is both server side and client side logic to set the active state on nav items.
And by default, both server and client side logic is active for some reason.
Server Side Logic
The issue with using server-side logic for this is that you cannot cache the block since you are going to be caching that active state as well.
When you disable caching for the navigation menu, best case scenario is that you only invalidate full page cache, worse case is you invalidate some other blocks such as the header, etc depending on how your theme is set up.
The server side logic can be found in
It uses the
has_active properties on the passed tree node attribute,
which are set in
Client Side Logic
The downside to this method is, there is no active state marked until the JS loads.
And if the client has JS disabled then they will have no active indicator at all.
The function that handles the client-side logic is
the way this function works is that it searches the href of the nav items checking if one matches the current URL.
You can disable caching on the navigation block, this will also disable caching on the parent blocks. So if your
navigation block is within your header block, your header won't be cached either.
You can do this, by setting the
TTL on the
catalog.topnav xml node to
0 which is the same as overwriting the block and overwriting the cache lifetime as you have done is your edit.
<reference name="catalog.topnav" ttl="0"/>
Use the client side logic
The other method you have is to disable the active state being rendered on the server side and rely on the client side logic.
Magento\Theme\Block\Html\Topmenu::_getMenuItemClasses() method is private, we cannot use a plugin to modify the result of it.
You could overwrite the class using a preference and either
- call the parent method and remove
has-active from the final array
- redeclare the method excluding the logic where it adds the active class.