I recently looked into creating a CSS-only navigation menu for WordPress, in an attempt to lighten the page payload. That’s a story that I’ll post separately, but it made me realise just how much effort WordPress goes to when creating the menu.
Of course, it’s all behind the scenes and it probably takes very little elapsed time, but it made me wonder whether its possible to cache the HTML. I’d used WP transients before to cache some custom site elements such as maps and galleries which don’t change frequently, so what about menus?
It turns out that it’s really straightforward to do. That said, there is a potentially major disadvantage, depending on the styles you’re applying to the menu items. I’ll come back to that shortly.
There are very few lines of code to add to your theme’s functions.php.
add_filter( 'wp_nav_menu', 'mt_cache_menu', 10, 2);
function mt_cache_menu($menu, $args) {
set_transient ( 'mt_menu' , $menu, 3600 );
return $menu;
}
add_filter( 'pre_wp_nav_menu', 'mt_cached_menu_check', 10, 2);
function mt_cached_menu_check($return, $args) {
$menu = get_transient ( 'mt_menu');
if ($menu) return $menu.'<!-- Cached -->';
return null;
}
add_action ( 'wp_update_nav_menu', 'mt_delete_menu_cache', 10, 2);
function mt_delete_menu_cache($menu_id, $menu_data) {
delete_transient ( 'mt_menu');
}
The first function, using the wp_nav_menu hook, saves the menu HTML when it’s just been created by WP. You need to decide on the key for the cache transient, and how long the cache will persist, in numbers of seconds. In the example above I’ve set it only for 3600 seconds, but in reality you’d probably choose a much higher value. If you leave this parameter out, it will default to “0”, which means “do not expire”.
The second function uses the pre_wp_nav_menu hook. WP calls this hook just before it sets about creating the menu. If you return a non-null value, WP will use that as the HTML, and save itself the bother of generating the menu afresh. So get_transient looks to see if the transient exists, and if so, whether it’s expired. If we get a value back, it must be the HTML we stored there, so we return it to WP. Otherwise, we return null so that WP will regenerate the HTML.
The final function here is to delete the cache when a menu is updated. It’s a simple hook into the wp_update_nav_menu action which fires in the admin back-end as a menu is changed. We simply delete the transient, with the result that next time round, WP will have to recreate the HTML and we can cache the new code.
Now, that major downside …
The menu HTML elements are dynamically assigned CSS classes. You can see the list at https://developer.wordpress.org/reference/functions/wp_nav_menu/#menu-item-css-classes. Classes such as .current-menu-item are designed for you to use to style (maybe highlight) the menu item for the current page. And if you cache the output, that class will be frozen in the HTML as it was when the menu was saved away into the transient. It won’t reflect the current page any more.
Hmm. Well, as long as you’re not styling anything like that, the cached version will be OK to use. But it could be a big disadvantage to caching. I’ve seen some blog posts where people suggest caching the menu separately for every page (every post?) on the site. Well maybe, but on a site with a big menu structure, maybe not.
I’ve one site where this will not be an issue – the menu styling doesn’t use current page highlighting. So I’ll go with this caching approach and see how we get on.