This is one of the most misleading bugs I've ever chased on a WordPress site I maintain: the dropdown in the header menu worked perfectly on every other page but was completely dead on the homepage. Click (or hover) the trigger, and nothing happened. No red JavaScript error in the console. Nothing visibly broken. Just silence.
What made it worse: locally, everything worked. The bug only showed up in production. I spent nearly an hour staring at code that was, in fact, correct.
The deceptive symptom
The first thing I did was confirm the handler was actually attached. I opened the console on the production homepage and inspected the trigger element:
// in the console, on the dropdown trigger element
console.log( 'Click handler attached?', el.onclick );
// → Click handler attached? nullnull. No handler. But the JS file was clearly enqueued — I could fetch() it and read its contents:
fetch( '/wp-content/themes/my-theme/assets/js/nav.js' )
.then( r => r.text() )
.then( t => console.log( t.slice( 0, 200 ) ) );
// → the latest code, exactly what I deployed. Correct.So the file was there, its contents were correct, the latest code. But the handler was never attached. Correct code that never ran. That was the big clue.
Then I pasted the addEventListener logic manually into the console — and the dropdown came alive instantly. The moment I ran the code myself, everything worked. That was the "aha": if running the code manually fixes it, the problem isn't the code — it's when (or whether) that code runs.
The cause: Delay JavaScript Execution
The culprit was WP Rocket's Delay JavaScript Execution feature. It defers all JavaScript execution until the first user interaction (click, scroll, touch, keypress). It's great for performance scores: don't run JS until the user actually interacts, so your initial load metrics look better.
But here's the trap: hover does not count as a triggering interaction. On the homepage, the first thing a user often does is move the mouse over the menu to open the dropdown. At the moment that hover happens, the navigation JavaScript has not run yet — so addEventListener hasn't had a chance to attach its handler. The trigger is visually alive but functionally dead, because the code meant to wire it up is still being held back.
That also explains why it "works on other pages": on inner pages, the user has usually already clicked or scrolled before touching the menu, and that interaction has already released the delayed JS. On the homepage, the dropdown is often the very first interaction — so it loses the race.
This deferral also explains why it always worked locally: WP Rocket usually isn't that aggressive (or isn't active at all) outside production.
The fix: exclude the JS file from delay
You don't need to disable Delay JavaScript Execution entirely — it's a useful performance feature. You just exclude the file that has to run early:
WP Rocket → File Optimization → JavaScript tab → Delay JavaScript Execution → Excluded JavaScript Files
Add the path (or a unique substring) of your theme's JS there, for example:
/wp-content/themes/my-theme/assets/js/nav.js
Then clear the WP Rocket cache. After that, the navigation file runs normally on load, addEventListener attaches before the user can hover, and the dropdown comes alive on the homepage exactly as it should.
How to quickly diagnose "correct code that won't run"
Whenever a frontend feature dies in production but the code is clearly correct, run this sequence:
- Check whether the handler is actually attached —
el.onclickor inspect the listeners. If it'snull, the attaching code hasn't run. fetch()the JS file and read its contents — confirm the latest code actually shipped, not an old cached version.- Paste the logic manually into the console. If that fixes it, the problem is execution timing, not the code itself.
- Suspect JS delay/defer. In WP Rocket it's Delay JavaScript Execution; it could also be a
defer/asyncattribute, or some other lazy-init. Exclude the script that matters.
The lesson
The most time-consuming bugs often aren't about wrong code, but about code that is correct yet never runs. Delay JavaScript Execution optimizes your performance score by holding JS back until the first interaction — and hover quietly doesn't count, so anything triggered by hover can die without a single error. When something fails only in production while the code is perfect, stop re-reading the code: check when it runs. If pasting it into the console fixes it, you're looking at a delay problem, not a code problem.
