How to Fix Poor INP (Interaction to Next Paint) When Clicks Lag on WordPress
Search Console shows INP in red and buttons feel sticky. Learn what INP measures, find the long tasks behind it in Chrome devtools, and fix the scripts causing them.
Your speed plugin reordered or delayed JavaScript and now menus, sliders, popups or forms are dead. Find the failing script and exclude it properly.
You switched on “Delay JavaScript execution”, “Load JavaScript deferred”, “Combine JavaScript files” or “Minify JavaScript” and your speed score went up. Then someone told you the mobile menu does not open, the hero slider is a stack of images, the cookie banner never appears, or the contact form submits to nowhere. In the browser Console you may see Uncaught ReferenceError: jQuery is not defined, Uncaught TypeError: $ is not a function or Uncaught ReferenceError: SOMETHING is not defined.
Nothing is wrong with your theme or your builder. The optimization changed when and in what order your scripts run, and one script now runs before the thing it depends on exists, or does not run until someone moves the mouse. This guide is for site owners, designers and freelancers who use WP Rocket, LiteSpeed Cache, Autoptimize, Perfmatters, FlyingPress, SiteGround Optimizer or a similar tool. If your whole layout is unstyled instead, that is a CSS problem and is covered in the CSS not loading guide.
A browser reads your HTML from top to bottom. A normal <script src="..."> tag stops the parser, downloads the file, runs it, and only then continues. WordPress relies on that order: it prints jQuery first, then jQuery Migrate, then every plugin file that declared jQuery as a dependency, and it prints small inline scripts (configuration objects, translations, nonces) right before or after the file that needs them.
Speed plugins change that model in four different ways. Each breaks things in its own way.
| Optimization | What it does | Typical breakage |
|---|---|---|
| Defer | Adds the defer attribute so files download in parallel and run after the HTML is parsed, in document order. |
Inline scripts that call jQuery(...) run immediately, but jQuery itself is now deferred, so you get “jQuery is not defined”. |
| Delay until interaction | Rewrites script tags so they do not run at all until the visitor scrolls, moves the mouse, taps or presses a key (or a timeout fires, if the plugin has one). | Anything that must work on first paint is dead: sliders, above the fold animations, the menu toggle, cookie banners, chat widgets, lazy loaded hero images. |
| Combine | Concatenates many files into one or a few bundles. | One syntax error or a file that expects to be global breaks the whole bundle. Order inside the bundle may differ from the original order. |
| Minify | Strips whitespace and comments, sometimes rewrites code. | A file that relies on automatic semicolon insertion, or that was already minified, can be mangled into a syntax error. |
The key idea is dependency. A script that calls jQuery, $, wp.i18n, Swiper or elementorFrontendConfig needs that name to exist at the moment it runs. If it runs too early, it throws a ReferenceError and stops. Every later line in that script is skipped, which is why one error can kill a menu, a slider and a form at once.
About “$ is not a function”: WordPress loads jQuery in noConflict mode, so the global $ is not set to jQuery. Code written as jQuery(function($){ ... }) is fine. Code that uses bare $ at the top level only worked before because some other script happened to define it. Reordering exposes that hidden bug.
curl.exe in PowerShell). Server commands run over SSH in the site folder, the one that contains wp-config.php, using wp or wp-cli depending on how your host installed WP-CLI.Several plugins have a URL parameter that serves the page without their optimizations. Only use parameters the plugin documents:
| Plugin | Bypass | Notes |
|---|---|---|
| WP Rocket | ?nowprocket |
Skips WP Rocket caching and its file, media and CDN optimizations. A host or Cloudflare cache may still serve a cached copy depending on how it treats query strings. |
| LiteSpeed Cache | ?LSCWP_CTRL=before_optm |
Shows the page before page optimization is applied. |
| Autoptimize | ?ao_noptimize=1 |
Disables Autoptimize for that request. |
For any other plugin, do not guess a parameter; turn off its JavaScript options and purge instead.
Open the broken page in a private window, then open https://example.com/PAGE_PATH/?nowprocket (or the matching parameter). If the menu or form works on the bypass URL and fails on the normal URL, you have confirmed the cause. If it fails on both, the problem is not the optimization; check FG-07 for builder update problems instead.
In the plugin settings, switch off JavaScript options in this order and purge all caches after each change: Delay JavaScript, then Defer, then Combine, then Minify. The first switch that fixes the page tells you which mechanism you are dealing with, and the rest of this guide tells you how to keep that optimization on while excluding only what needs excluding.
Open DevTools, select the Console tab, check “Preserve log” so errors survive a reload, then reload the page. Red errors appear in the order they happened. The first one is usually the cause and the rest are knock on effects.
Uncaught ReferenceError: jQuery is not defined
at example.com/:412:5
Uncaught TypeError: Cannot read properties of undefined (reading 'init')
at frontend.min.js?ver=VERSION:2:10871
This is example Console output. The first line says inline code on line 412 of the page itself (the example.com/ location) called jQuery before jQuery existed. VERSION stands for whatever version string your file carries. The second error is a consequence: a builder script expected an object that the first, failed script should have created. Fix the first and the second usually disappears.
Click the blue file and line link on the right of the error. DevTools jumps to the Sources panel with the line highlighted. If the file is a combined or minified bundle, click the {} “Pretty print” button at the bottom of the editor to make it readable. Look at the surrounding code for a recognizable name, such as a plugin slug in a comment, a variable like wpcf7 (Contact Form 7), elementorFrontend or gform (Gravity Forms). That tells you which plugin owns the code.
In the Elements panel press Ctrl+F (Cmd+F on a Mac) and search for the plugin slug. When a script is delayed, the tag looks different from normal. WP Rocket, for example, changes delayed tags to type="rocketlazyloadscript" and moves src into a data-rocket-src attribute. Other plugins use their own type values. If the script you need on first paint has one of these rewritten types, it is being delayed and needs an exclusion.
Open the Network tab, filter by JS, and reload. A combined file returning 404 (usually because a cache was purged without regenerating the minified files, or because a CDN cached an old HTML page pointing to a deleted bundle) produces errors that look like dependency bugs. Clear the plugin cache and the CDN cache together, then reload.
View the page source (Ctrl+U) and search for jquery.min.js. Note whether it has defer, a delayed type, or has been folded into a combined file. Then search for the inline script on the line number from the Console. If that inline block calls jQuery and appears above the point where jQuery actually runs, you have found the dependency break.
/jquery-?[0-9.](.*)(.min|.slim|.slim.min)?.js and jquery-migrate(.min)?.js to the exclusion box (WP Rocket accepts partial paths and regular expression style patterns in that field)./wp-includes/js/jquery/jquery.min.js there. On WP Rocket, excluding jQuery from Load JavaScript Deferred also causes inline scripts to be left alone, which is usually exactly what you want.If the failing code is yours (a snippet in a code snippets plugin, a builder custom code area or a child theme), wrap it so it does not rely on a global $:
jQuery(function ($) {
// your existing code that uses $ goes here
});
This goes in the file or snippet box that contains your custom JavaScript. It has no placeholders. jQuery(function ($) { ... }) waits for the DOM to be ready and passes jQuery in as $ for the code inside. Good result: the “$ is not a function” error disappears from the Console. Bad result: you now see “jQuery is not defined” instead, which means jQuery itself is still delayed or deferred after your snippet; go back to the previous section.
Delay until interaction is the most aggressive optimization and the most common cause of “the slider is just a pile of images” and “the menu does nothing on mobile”. It is also the most valuable for scores, so the goal is to exclude only what is visible and interactive on first view.
Load the page in a private window and do not touch anything for ten seconds. If the slider starts or the menu begins working the moment you move the mouse or scroll, delay is the cause. On phones the first tap is often consumed as the “interaction” that wakes the scripts, so the first tap on the menu button does nothing and the second works. That pattern is a clear signature.
| Feature | Exclusion pattern to try |
|---|---|
| jQuery and Migrate | /jquery-?[0-9.](.*)(.min|.slim|.slim.min)?.js and jquery-migrate(.min)?.js |
| Elementor frontend (menus, sliders, popups) | /elementor/assets/js/, /elementor-pro/assets/js/, elementorFrontendConfig, ElementorProFrontendConfig |
| Swiper based sliders | swiper |
| Slider Revolution | /revslider/, setREVStartSize, revapi |
| Theme navigation script | /themes/THEME_SLUG/, or the exact file such as /themes/THEME_SLUG/js/navigation.js |
| Cookie consent banner | The plugin folder, for example /plugins/PLUGIN_SLUG/, plus its inline config variable name |
Replace THEME_SLUG with your theme’s folder name (shown in the file path in the Sources panel, or under wp-content/themes/) and PLUGIN_SLUG with the plugin’s folder name under wp-content/plugins/. The patterns are matched against the script URL or the inline script content, so a variable name such as elementorFrontendConfig catches the inline configuration block that the external file needs.
Before typing patterns by hand, check whether your plugin offers a ready made list. WP Rocket 3.13 and later has “One-click exclusions” under the Delay JavaScript setting, with checkboxes for common plugins, themes and services. WP Rocket also has a “Safe Mode” for Delay JavaScript that excludes jQuery, Migrate and most scripts under /wp-content/ and /wp-includes/. Treat it as a diagnostic: turn it on, confirm the page works, then turn it off and add narrow exclusions instead.
Dependencies form a chain. If you exclude slider.min.js from delay but jQuery is still delayed, the slider throws “jQuery is not defined” earlier than before. When you exclude a script, also exclude everything it depends on: jQuery, any library it uses, and its inline configuration block. The Console will tell you what is missing; keep adding the next missing name until the page is clean.
WordPress attaches inline code to registered scripts with wp_add_inline_script() and wp_localize_script(). In the page source these appear with ids ending in -js-before, -js-after or -js-extra, for example <script id="contact-form-7-js-extra">. The -js-extra and -js-before blocks usually define configuration objects the file needs; -js-after blocks usually call into the file.
Problems appear when the plugin defers the external file but leaves the inline -js-after block running immediately. It calls a function that does not exist yet. The fixes, in order of preference:
-js-after block.Forms are a frequent victim here because their nonce or REST URL lives in an inline -js-extra block. If the form “submits” but the page simply reloads, or the spinner never stops, check the Console for an undefined configuration object such as wpcf7.
jQuery Migrate restores deprecated jQuery functions for older plugins. Some speed plugins and themes offer “Remove jQuery Migrate”. If an old slider or lightbox then calls a removed method, you see errors such as jQuery(...).live is not a function. Search the page source for jquery-migrate; if it is missing and you see these errors, re-enable Migrate, then update or replace the plugin named in the stack trace.
A minify or combine break shows as Uncaught SyntaxError: Unexpected token or missing ) after argument list pointing into a cached file under a path such as /wp-content/cache/min/ or /wp-content/litespeed/js/. A SyntaxError means the browser could not parse the file at all, so nothing in it ran.
Pretty print the bundle in Sources, identify the original file around the error, and exclude it from minify or combine. If you cannot tell, turn combine off. Purge afterward, because old bundles stay on disk and in CDN caches.
Random toggling wastes hours. After every change: purge the plugin, server and CDN caches; open a fresh private window; wait five seconds without touching anything; then open the mobile menu, every slider, tab set and popup, submit each form with test data, and walk through cart and checkout if you have a shop. Check the Console for red errors on each page, and repeat on a real phone.
Enable one optimization, run the script, fix what broke with narrow exclusions, then enable the next.
When the Console does not make the order obvious, watch execution directly.
In Sources, open the right sidebar, expand “Event Listener Breakpoints”, then “Script”, and check “Script First Statement”. Reload. DevTools pauses at the first line of every script as it starts, in the real order the browser runs them. Press F8 to continue to the next one. Watch where jQuery runs relative to the script that fails. This is the fastest way to see what a combine or delay setting really did.
At any pause, type a name into the Console, such as typeof jQuery. A result of "undefined" at the point where the failing script starts proves the dependency is not loaded yet. "function" means it is present and you should look elsewhere.
curl -s "https://example.com/PAGE_PATH/" > optimized.html
curl -s "https://example.com/PAGE_PATH/?nowprocket" > original.html
diff <(grep -o '<script[^>]*' original.html) <(grep -o '<script[^>]*' optimized.html)
Run this on your own computer in bash or zsh (the <( ) syntax does not work in PowerShell; use WSL). Replace the URL and use your plugin’s documented bypass parameter. The first two lines save both versions of the page; the third compares only their script tags. Lines starting with < exist only in the original, and lines starting with > exist only in the optimized page. A good diff shows only attribute changes on scripts you are happy to delay. A bad diff shows your menu, slider or form script turned into a delayed type, or missing entirely because it was folded into a bundle.
Delaying chat widgets is usually fine, but reCAPTCHA and payment scripts on form and checkout pages should be excluded so the form is ready on arrival. Exclude by domain, for example recaptcha or js.stripe.com.
Open a ticket when a server layer looks involved:
Send the exact page URL, the UTC time you tested, the first Console error copied as text, the response headers of the page from the Network tab, the list of optimizations you have on, and which bypass URL you tried. Mention what changed and when. If the site is fully down, go to the Emergency Room; the diagnostic tools help you collect headers.
Did this fix it?
Still stuck? Start again from what you can see in the Emergency Room.
Still stuck?
Print a free one-page checklist to keep, or open the Emergency Room if the symptom does not match.
Get the free checklistsKeep reading
Search the field manual
Or jump straight to a symptom
Press / to search from any page, Esc to close