WordPress Multisite Speed Improvements – Admin Bar

The WP admin bar is certainly handy and the dropdown menu listing the sites in the current network is great for quickly switching between them. Unfortunately this same dropdown menu can also cause slow site speeds, particularly in a large multisite network. To understand why a dropdown menu can be slow and how it can be improved let’s take a look at the code.

The problem with the Admin Bar

If we dig deep into the wp-includes/class-wp-admin-bar.php file we find a method called add_menus which (unsurprisingly!) is where all the menu nodes are registered for the admin bar. Using an action called ‘admin_bar_menu’ we can see that each node in the menu corresponds to a registered function. The function that deals with the sites drop down is called wp_admin_bar_my_sites_menu and is located in the wp-includes/admin-bar.php file.

If we take a look at this function we see it does a few basic permission checks, sets up the network menu if the user is a super admin and finally gets all the blogs the user is a member of ($wp_admin_bar->user->blogs is assigned in the WP_Admin_Bar class) cycles through them and creates the appropriate menu links.

In order to create these links for each of the sites in the dropdown it calls a function called switch_to_blog(), a handy multisite function that temporarily replaces all global blog variables with new ones for another blog on your network. The problem is it’s slow. Really, really slow. If you have anything like a large multisite install then the fact it’s being called on every page load, for every logged in user, to switch to every site in the network is going to have a significant affect on your site’s performance.

The Solution

The obvious answer is to not use it and create the links some other way. So that’s exactly what we’re going to do. Here’s how.

Firstly, de-register the old function:

add_action( 'add_admin_bar_menus', 'large_network_remove_wp_admin_bar_my_sites_menu', 10, 0 );

/**
 * De-register the native WP Admin Bar My Sites function.
 *
 * Loading all sites menu for large multisite is inefficient and bad news. This de-registers the native WP function so it can be replaced with a more efficient one.
 *
 * @return null
 */
public function large_network_remove_wp_admin_bar_my_sites_menu() {
	remove_action( 'admin_bar_menu', 'wp_admin_bar_my_sites_menu', 20 );
}

Now add in this new function. It’s mostly the same as the original:

add_action( 'admin_bar_menu', 'large_network_replacement_my_sites_menu', 20, 1 );


/**
 * Add the "My Sites/[Site Name]" menu and all submenus.
 *
 * Essentially the same as the WP native one but doesn't use switch_to_blog();
 *
 *
 * @param WP_Admin_Bar $wp_admin_bar
 * @return null
 */
public function large_network_replacement_my_sites_menu( $wp_admin_bar ) {
	// Don't show for logged out users or single site mode.
	if ( ! is_user_logged_in() || ! is_multisite() )
		return;

	// Show only when the user has at least one site, or they're a super admin.
	if ( count( $wp_admin_bar->user->blogs ) < 1 && ! is_super_admin() ) return; if ( $wp_admin_bar->user->active_blog ) {
		$my_sites_url = get_admin_url( $wp_admin_bar->user->active_blog->blog_id, 'my-sites.php' );
	} else {
		$my_sites_url = admin_url( 'my-sites.php' );
	}

	$wp_admin_bar->add_menu( array(
		'id'    => 'my-sites',
		'title' => __( 'My Sites' ),
		'href'  => $my_sites_url,
	) );

	if ( is_super_admin() ) {
		$wp_admin_bar->add_group( array(
			'parent' => 'my-sites',
			'id'     => 'my-sites-super-admin',
		) );

		$wp_admin_bar->add_menu( array(
			'parent' => 'my-sites-super-admin',
			'id'     => 'network-admin',
			'title'  => __('Network Admin'),
			'href'   => network_admin_url(),
		) );

		$wp_admin_bar->add_menu( array(
			'parent' => 'network-admin',
			'id'     => 'network-admin-d',
			'title'  => __( 'Dashboard' ),
			'href'   => network_admin_url(),
		) );
		$wp_admin_bar->add_menu( array(
			'parent' => 'network-admin',
			'id'     => 'network-admin-s',
			'title'  => __( 'Sites' ),
			'href'   => network_admin_url( 'sites.php' ),
		) );
		$wp_admin_bar->add_menu( array(
			'parent' => 'network-admin',
			'id'     => 'network-admin-u',
			'title'  => __( 'Users' ),
			'href'   => network_admin_url( 'users.php' ),
		) );
		$wp_admin_bar->add_menu( array(
			'parent' => 'network-admin',
			'id'     => 'network-admin-t',
			'title'  => __( 'Themes' ),
			'href'   => network_admin_url( 'themes.php' ),
		) );
		$wp_admin_bar->add_menu( array(
			'parent' => 'network-admin',
			'id'     => 'network-admin-p',
			'title'  => __( 'Plugins' ),
			'href'   => network_admin_url( 'plugins.php' ),
		) );
		$wp_admin_bar->add_menu( array(
			'parent' => 'network-admin',
			'id'     => 'network-admin-o',
			'title'  => __( 'Settings' ),
			'href'   => network_admin_url( 'settings.php' ),
		) );
	}

	// Add site links
	$wp_admin_bar->add_group( array(
		'parent' => 'my-sites',
		'id'     => 'my-sites-list',
		'meta'   => array(
			'class' => is_super_admin() ? 'ab-sub-secondary' : '',
		),
	) );

	foreach ( (array) $wp_admin_bar->user->blogs as $blog ) {

		$blavatar = '
<div class="blavatar"></div>

';

		$blogname = $blog->blogname;

		if ( ! $blogname ) {
			$blogname = preg_replace( '#^(https?://)?(www.)?#', '', $blog->siteurl );
		}

		$menu_id  = 'blog-' . $blog->userblog_id;

		$admin_url = $blog->siteurl . '/wp-admin';

		$wp_admin_bar->add_menu( array(
			'parent'    => 'my-sites-list',
			'id'        => $menu_id,
			'title'     => $blavatar . $blogname,
			'href'      => $admin_url,
		) );

		$wp_admin_bar->add_menu( array(
			'parent' => $menu_id,
			'id'     => $menu_id . '-d',
			'title'  => __( 'Dashboard' ),
			'href'   => $admin_url,
		) );

		$wp_admin_bar->add_menu( array(
			'parent' => $menu_id,
			'id'     => $menu_id . '-v',
			'title'  => __( 'Visit Site' ),
			'href'   => $blog->siteurl,
		) );

	}
}

Unfortunately we’ve had to remove the “New Post” & “Manage Comments” links. To add them in we’d have to check the user’s permissions on that blog and one way or another that would involve using switch_to_blog(). I don’t see it as much of a loss however.

The site I added this to had noticeable speed improvements after implementing so if you do have a large multisite environment or a lot of users then it’s definitely worth doing.

Comments or questions, post below!