DEV Community

Cover image for When Delay JS Breaks a WordPress Mobile Menu (and PageSpeed Looks Fine)
Bohdan Prytulyak
Bohdan Prytulyak

Posted on AI-assisted

When Delay JS Breaks a WordPress Mobile Menu (and PageSpeed Looks Fine)

My WordPress website was scoring in the high 90s on mobile PageSpeed Insights. The layout looked right, the navigation worked on desktop, and the mobile view in browser DevTools behaved as expected.

Then I picked up my phone and tapped the hamburger menu.

Nothing happened.

I tapped again. Sometimes the menu opened, sometimes it didn't. On a site where navigation is one of the first things people use, this is not a small detail. It also made the PageSpeed result feel rather less impressive.

This is a short account of what I tested, what changed, and what I still couldn't confirm. It wasn't a controlled performance experiment, and I don't want to turn one incident into a universal rule about WordPress caching.

The setup: Kadence, LiteSpeed Cache, and delayed JavaScript

The site uses WordPress with the Kadence theme and LiteSpeed Cache. I had been working on mobile performance for some time. The homepage regularly returned Performance scores around 97–99, and other pages often scored even higher.

One of the optimizations in use was Load JS Deferred → Delayed in LiteSpeed Cache.

The idea is straightforward: JavaScript that is not needed to render the initial page can wait until there is user activity. That can reduce the amount of work the browser does at the beginning of the page load.

But there is an obvious risk. What if the script being delayed is the one that handles the user's first interaction?

In my case, that interaction was opening the mobile navigation drawer. The button looked fine. The layout wasn't visibly broken. The problem was entirely in how it behaved when tapped.

Why the browser's mobile emulator didn't catch it

My usual desktop checks didn't reproduce the problem. Even with a mobile viewport selected in DevTools, the menu opened normally.

The physical phone told a different story.

That doesn't prove that touch events were the cause. A real phone and a desktop emulator may differ in processing speed, browser state, cached resources, or the timing of script execution. I hadn't isolated those differences one by one.

What the incident did establish was that passing a viewport-based mobile check wasn't enough. The failing interaction occurred in the environment that actually mattered to the visitor.

The test that narrowed down the problem

Rather than immediately editing Kadence or adding CSS overrides, I tested the page without LiteSpeed's Page Optimization layer.

LiteSpeed documents a query parameter for this kind of troubleshooting:

https://example.com/?LSCWP_CTRL=before_optm
Enter fullscreen mode Exit fullscreen mode

I opened my own site's equivalent URL directly on the phone. With Page Optimization bypassed for that view, the mobile menu responded.

That was the useful result. It didn't identify one particular line of JavaScript, but it gave me a sensible boundary: the problem appeared with frontend optimization enabled and disappeared when that optimization was bypassed.

The official LiteSpeed CSS/JS troubleshooting guide describes the same comparison and recommends isolating the affected CSS or JS assets rather than changing everything at once.

Excluding the navigation scripts

The next step was to prevent the Kadence navigation scripts from being delayed. In LiteSpeed Cache → Page Optimization → Tuning → JS Deferred/Delayed Excludes, I added:

navigation.min.js
navigation-lite.min.js
Enter fullscreen mode Exit fullscreen mode

I also added the same entries to Guest Mode JS Excludes, because the behavior I was investigating affected anonymous visitors.

These are filename-matching exclusions, not a recommendation to paste the same two lines into every WordPress installation. The right files depend on the theme, its version, and the scripts loaded on the page. The relevant fields are described in the LiteSpeed Page Optimization documentation.

Then I saved the settings, purged the cache, and tested again on the phone.

LiteSpeed Cache settings

LiteSpeed Cache settings: Kadence navigation scripts excluded from delayed JavaScript execution and Guest Mode optimization.

The behavior improved, but I wouldn't call that a complete fix. In a private browser session, the menu began opening again, although it initially still required a second tap in some cases. My regular browser session continued to show inconsistent behavior at that point.

That distinction matters: the bypass test strongly implicated the optimization layer, and the exclusions changed the behavior. They did not establish that every first-tap issue had been eliminated.

Why testing can get confusing after a cache change

After adjusting an optimization setting, the browser may still be looking at resources or HTML produced under the old configuration. Guest optimization and ordinary page caching can also give different visitors different versions of a page.

In other words, there are at least two separate questions:

  1. Did the new configuration stop delaying the script we wanted to run early?
  2. Is the phone actually receiving the new page and resources?

Those questions are easy to mix up when the only test is tapping the menu once and refreshing the page.

For troubleshooting, I prefer to keep the comparison explicit: normal URL versus optimization-bypass URL; regular browser versus private session; first page load versus repeat visit. Then I inspect what actually loaded before assuming a setting worked or failed.

A high Lighthouse score is not an interaction test

The irony is that the page was performing well in the metrics I had been optimizing.

This wasn't a case of a visibly slow WordPress installation. My mobile Performance scores were already around 97–99. But a visitor who couldn't open the navigation on the first tap wouldn't care about that number.

Lighthouse is useful for measuring and investigating performance. It isn't a substitute for verifying the workflows that make a website usable. A good score doesn't tell you whether every dropdown, search field, filter, form, or checkout action works.

I have seen the same problem with oversimplified performance advice about WordPress plugins. In a comparison based on two production websites, one site had a single active plugin and scored 45 on mobile Performance, while another had 18 active plugin entries and scored in the high 90s. That was not a controlled benchmark; it was a reminder to measure what a website does instead of trusting a simple number.

A navigation drawer may be a small piece of UI, but it can be essential for someone trying to reach a service page from a phone.

And delaying code can make a test look faster precisely because less JavaScript has run during the measurement. That improvement needs to be weighed against the behavior users experience when they start interacting with the page.

What I'd test after changing JavaScript optimization

I wouldn't start by disabling every performance feature. First, I would identify the failing interaction and test whether it still fails with the optimization layer bypassed.

If it doesn't, I would inspect the actual scripts involved, exclude the likely dependency from delay, purge the relevant caches, and retest. For a WordPress site, the short list of interactions worth checking usually includes the mobile menu, forms, popups, search, filters, sliders, and any commerce-related buttons.

I also want at least one test on a physical phone. DevTools remains useful, but it did not reproduce this particular problem.

A few checks matter more to me now than another point in PageSpeed:

  • Does the menu open on the first tap, not just the second?
  • Does it work in a private session and a returning visitor's session?
  • Does the behavior remain consistent after a hard reload and after a normal revisit?
  • Are important scripts running when the interaction needs them, rather than after it?

That is a much more practical definition of a successful optimization than a score alone.

I still use Delay JS

This incident didn't convince me to stop using delayed JavaScript. Used selectively, it is a valuable optimization.

It did change how I think about the trade-off. A script that is not needed for the initial render may still be critical for the first action a visitor takes. Delaying it without testing that action can make a page look excellent in a performance report while making it less useful in practice.

I'm comfortable with 97 instead of 100 if the site behaves correctly. I'm not comfortable with 100 if someone has to tap the menu twice to reach the rest of the website.

Performance work is finished only when the faster page is still a usable page.

Top comments (0)