Block Theme Performance

WordPress Font Library

WordPress 6.5 introduced a font library which allows themes and plugins to define custom font stacks which reference either:

  • native system fonts,
  • font files bundled with themes and plugins,
  • externally hosted fonts (such as Google Fonts).

Users can then use those fonts in the style editor of the block themes:

Font library interface in WordPress block theme style editor
Font library interface in the WordPress block theme editor.

When using either locally hosted or externally hosted fonts, WordPress uses the wp_head action with a callback to wp_print_font_faces() to add the font definitions:

add_action( 'wp_head', 'wp_print_font_faces', 50 );

which prints the following CSS with @font-face definitions in the <head>:

<style id="wp-fonts-local">
@font-face{font-family:Inter;font-style:normal;font-weight:300 900;font-display:block;src:url('https://example.com/wp-content/themes/twentytwentyfour/assets/fonts/inter/Inter-VariableFont_slnt,wght.woff2') format('woff2');font-stretch:normal;}
</style>

All other stylesheets can then reference the font by name which is “Inter” in this example.

The inline font definitions produced by wp_print_font_faces() point to font URLs which are a critical resource that must be loaded as soon as possible to avoid layout shift and to speed up the First Contentful Paint (FCP) and the Largest Contenful Paint (LCP) when used for prominent headings or menus.

Here is a comparison between the default behaviour and with the preload enabled:

Default WordPress custom fonts vs. preload enabled

Notice how the preloaded version has an extra font because it doesn’t know that the particular style isn’t used on the page.

How to Preload Custom Fonts?

Here is a snippet of PHP to add preload hints for the fonts to the <head> to ensure they’re loaded as early as possible:

function your_prefix_preload_theme_fonts() {
	// This is available only since WP 6.4.
	$fonts = WP_Font_Face_Resolver::get_fonts_from_theme_json();

	if ( empty( $fonts ) ) {
		return;
	}

	$srcs = wp_list_pluck( $fonts[0], 'src' );
	$srcs = array_filter( array_merge( ...$srcs ) ); // Flatten them.

	$preload = array_map(
		function ( $src ) {
			return sprintf(
				'<link rel="preload" href="%s" as="font" type="font/%s" crossorigin />',
				esc_url( $src ),
				esc_attr( pathinfo( $src, PATHINFO_EXTENSION ) )
			);
		},
		$srcs
	);

	echo implode( PHP_EOL, $preload );
}

add_action( 'wp_head', 'your_prefix_preload_theme_fonts', 6 ); // Add early in the <head>.

Limitations of Preloading

Please consider the preloading limitations when using this approach:

  • Fonts or their specific styles and weights might not be used on all requests (like the example above) which is not desired.
  • Fonts can have multiple format types and the browsers decide which one to use. Preloading different formats of the same font is too excessive.
  • Preloading is not aware of the unicode-range filtering applied during the @font-face definition so it will fetch the whole file instead of just the required characters.

Discussion

2 responses

  1. Something else to consider is adding a media query to the preload links in case the font is only LCP or visible on certain viewport sizes.

    I did some benchmarking to see if preloading fonts would improve performance but unfortunately in my test I saw the opposite where it increased LCP, which was very confusing to me: https://github.com/WordPress/performance/issues/1313#issuecomment-2499165109

    This is in the context of Performance Lab where I was wanting to implement the automatic addition of the font preload links via Optimization Detective.

    1. Thanks for sharing your experience! I was very curious about the reasoning for the preload impact so I created this test suite which provides query flags for various features like font loading delays, font-display attribute values, etc.

      The benchmark-web-vitals test appears to have lower LCP in every test case without the preload enabled. However, inspecting manually I see that LCP is triggered even before the font is fully loaded. Will continue testing.

Leave a Reply

Your email address will not be published. Required fields are marked *