Once images and hosting are addressed, the next major contributor to a slow WordPress site is usually the combined weight of fonts, CSS, and JavaScript, much of it added by plugins rather than the theme itself. Each of these behaves differently in the browser’s loading sequence, and understanding those differences is what makes it possible to reduce their impact without breaking the site’s design or functionality.
How web fonts load, and why that matters
A browser cannot use a custom web font until it has downloaded the relevant font file, which creates a race between rendering the page’s text and finishing that download. How this race is handled is controlled largely by the CSS font-display property, which offers several distinct behaviors: swap displays text in a fallback system font immediately and swaps to the custom font once it arrives (avoiding invisible text, at the cost of a visible font swap); optional gives the custom font a very brief window (commonly around 100 milliseconds) to arrive, and if it misses that window, keeps the fallback font for the rest of that page view, avoiding a later swap and its associated layout shift; and block hides text entirely until the font arrives, which is generally discouraged for body content since it risks a longer period of invisible text.
There is no single universally correct choice. Long-form body text, where visual stability matters most, often benefits from optional, while contexts where showing the content as quickly as possible matters more than avoiding a swap may favor swap. Icon fonts, where displaying the wrong glyph is worse than displaying nothing, are one of the few cases where block is still commonly used.
Local versus external font hosting
Fonts can be loaded from a third-party service (most commonly through an external stylesheet or API), or self-hosted from the same origin as the rest of the site. Self-hosting can remove an additional connection to a third-party domain and gives direct control over file formats, subsetting, caching headers, and preload decisions. It is not automatically faster, though: the outcome depends on how efficiently either option is configured. A well-cached, compressed WOFF2 font delivered over modern HTTP can perform well from either location. If you self-host, use long-lived caching, serve only the weights and character sets you need, and consider a CDN when it genuinely improves delivery to your audience rather than treating it as a requirement.
A separate practical point: modern browsers partition their cache by the top-level site being visited, for privacy reasons. This means the older argument that a widely used external font service benefits from “already being cached from another site” no longer holds in current browsers, since a font cached while visiting one site is not reused on a different site even if both reference the identical external font file.
Font weights and subsetting
Every additional font weight and style (regular, bold, italic, and so on) is a separate file to download. Limiting a design to the smallest practical number of weights, often just one or two, directly reduces total font payload. Where a typeface is available as a variable font, a single file can cover a full range of weights, which can reduce the total number of font requests compared to loading several separate static weight files. Subsetting, limiting a font file to only the character set actually used by the site’s content and language, can also meaningfully reduce file size, particularly for large font families with broad international character support that a specific English-language site does not need.
Render-blocking CSS
By default, a browser will not render any page content until it has downloaded and parsed all CSS referenced in the document head, since CSS can affect the visual presentation of every element on the page. A large stylesheet, or several separate stylesheets loaded sequentially, can therefore delay the first paint of the page even if the underlying HTML is ready far earlier. This directly affects the “element render delay” portion of Largest Contentful Paint, covered in more detail in Largest Contentful Paint in WordPress: How to Find and Fix It. This is also one of the reasons a WordPress site with many active plugins, each enqueuing its own stylesheet, can feel slower to first render even when the actual content is simple.
Unused CSS and critical CSS
Most WordPress themes and plugins load a general-purpose stylesheet that includes styling for elements that may not appear on every page: a comment form styles rules on a page with comments disabled, gallery styles on a page with no gallery, and so on. This unused CSS still has to be downloaded and parsed before render, even though none of it is applied. Reducing this generally involves either removing unnecessary plugin stylesheets from pages that don’t need them, or generating a smaller “critical CSS” file, the minimal set of styles needed to render the visible, above-the-fold portion of a specific page, and loading that inline or early while deferring the remainder of the stylesheet until after initial render. Critical CSS is effective but requires ongoing regeneration whenever page layouts or content change meaningfully, since a stale critical CSS file can cause a flash of unstyled or incorrectly styled content.
JavaScript loading and the main thread
By default, when a browser’s HTML parser encounters a <script> tag, it pauses parsing the rest of the document to fetch (if external) and execute that script, before continuing. On a page with many scripts loaded this way, this can add up to a meaningful delay before the page becomes interactive, and can also affect Interaction to Next Paint if scripts continue executing long tasks on the main thread after the page has visually loaded.
Defer and async, and how WordPress handles them
The defer and async HTML script attributes both allow a script to download without blocking HTML parsing, but they behave differently once downloaded. A deferred script executes only after the full document has been parsed, and multiple deferred scripts execute in their original order, in position. An async script executes as soon as it finishes downloading, whichever script that happens to be, without a guaranteed order relative to other scripts. Deferred loading is generally the safer default for scripts that depend on the page’s DOM structure or on a specific execution order relative to other scripts, while async is often suitable for independent, self-contained scripts such as certain analytics or third-party embeds that don’t depend on anything else on the page.
Since WordPress 6.3, core supports specifying this behavior directly through the strategy argument when a script is registered or enqueued via wp_register_script() or wp_enqueue_script(), accepting a value of defer or async. Prior to this, developers relied on the older wp_script_add_data() function or manual output filtering to add these attributes, which is less standardized and more prone to inconsistent behavior across different scripts and dependencies. If a theme or plugin was built before WordPress 6.3, or hasn’t been updated to use the newer approach, it may still rely on these older methods, or may not defer its scripts at all.
Plugin asset weight
Every active plugin has the potential to add its own CSS and JavaScript to every page, regardless of whether that specific page uses the plugin’s functionality. A contact form plugin, for example, may load its validation script and styling on every page of the site, not just the page containing the actual form, unless the plugin (or a separate asset-management step) is configured to load conditionally. Auditing which plugins are enqueuing assets on pages that don’t need them, using the Network panel in browser developer tools while browsing representative pages, is a practical way to identify this kind of unnecessary weight.
Page-specific loading
Not every script or stylesheet needs to load on every page. Conditionally enqueuing assets, so that, for example, a gallery plugin’s CSS only loads on pages actually containing a gallery block, reduces average page weight across the site without removing any functionality where it’s genuinely needed. Some caching or optimization plugins offer this as a configurable feature (caching itself is covered separately in WordPress Caching Explained: Which Layers You Actually Need); it can also be implemented directly in a child theme’s functions file using WordPress’s conditional tags alongside the relevant script and style dequeuing functions.
Performance budgets
A performance budget is a self-imposed limit on a specific metric, for example, a maximum total CSS file size, a maximum number of separate JavaScript requests, or a maximum total page weight, set deliberately rather than discovered after the fact. Establishing a budget for a site’s typical article template, and checking new plugins or theme changes against it before they go live, helps prevent the kind of gradual, incremental weight increase that often causes a site to slow down over time without any single obvious cause.
Testing changes safely
Changes to font loading, CSS delivery, or script attributes can occasionally break page functionality, particularly when a script that depends on a specific load order is deferred or made asynchronous independently of its dependencies. Test any such change on a staging copy of the site first, check both visual rendering and interactive functionality (forms, menus, sliders, and any JavaScript-driven features) across representative page templates, and only then apply the same change to the live site. As covered in How to Measure WordPress Speed Before Making Any Changes, record a baseline before making these changes so the actual impact can be measured rather than assumed.
Common mistakes
- Loading several font weights or styles that the design doesn’t actually use
- Using
font-display: blockfor body text, causing a longer period of invisible text than necessary - Assuming self-hosting fonts automatically improves performance without checking file size, caching, subsetting, and delivery
- Leaving plugin CSS and JavaScript enqueued site-wide when it’s only needed on specific pages
- Deferring or making async a script whose dependencies are not also adjusted, breaking execution order
- Regenerating critical CSS once and never updating it as page layouts change
- Not testing script-loading changes on a staging site before applying them live
Practical checklist
- Limit font weights and styles to what the design actually uses
- Choose a deliberate
font-displayvalue based on whether visual stability or speed of text display matters more for that content - Confirm self-hosted fonts use efficient WOFF2 files, long-lived caching, sensible subsetting, and modern HTTP delivery before assuming they outperform an external service
- Audit plugin-enqueued CSS and JS using browser DevTools on representative pages
- Apply
defer(or, where safe,async) to non-critical scripts, using WordPress 6.3’sstrategyargument where available - Consider a critical CSS approach for the most-visited page templates, with a plan to regenerate it as layouts change
- Set a rough performance budget for your typical article template and check new plugins against it
Key Takeaways
- Fonts, CSS, and JavaScript each block the browser differently, and each needs a different optimization approach.
- Self-hosting fonts is not automatically faster than using an external font service; it depends on CDN and HTTP support behind the self-hosted files.
- Since WordPress 6.3, core supports a
strategyargument for cleanly applyingdeferorasyncto enqueued scripts. - Plugins frequently load CSS and JavaScript site-wide even when only a subset of pages need it; auditing this is one of the more effective, low-risk performance improvements available.
- Always test loading-behavior changes on staging first, since incorrect script ordering can silently break page functionality.
FAQs
Should I always self-host my fonts instead of using Google Fonts?
Not automatically. Self-hosting can help by removing an extra third-party connection and giving you more control, but the result still depends on font size, caching, subsetting, and delivery. A poorly configured self-hosted font can be slower than a well-configured external service, and the reverse can also be true.
What’s the safest font-display value to start with?
swap is a reasonable general default that avoids invisible text, though it can cause a visible font swap. optional avoids that swap entirely but may keep the fallback font for a visitor’s entire page view if the font arrives late.
Is async always faster than defer for JavaScript?
Not necessarily faster, but different: async scripts execute as soon as they finish downloading, with no guaranteed order, while defer preserves order and waits for full document parsing. Scripts that depend on the DOM or on other scripts generally need defer, not async.
How do I know which plugins are adding unnecessary CSS or JS to my pages?
Open browser DevTools, go to the Network panel, and load representative pages on the live or staging site. Stylesheets and scripts will be listed by source, making it possible to identify which plugin is responsible for assets that don’t seem relevant to that specific page.
Do I need to manually implement defer/async, or does a plugin handle it?
Both are possible. Some caching or optimization plugins offer a setting to apply these attributes automatically, while developers can also apply them directly using WordPress’s strategy argument (WordPress 6.3+) or the older wp_script_add_data() function on earlier versions. Either way, test thoroughly, since incorrect ordering can break dependent scripts.
Sources and further reading
- Google web.dev — Best practices for fonts; Optimize WebFont loading and rendering
- Make WordPress Core — Registering scripts with async and defer attributes in WordPress 6.3
- WordPress Developer Resources — wp_enqueue_script() and wp_register_script() function reference
- Google web.dev — Prevent layout shifting and flashes of invisible text by preloading optional fonts