1. Home
  2. Blog
  3. Performance Optimization

Defer JavaScript and remove unused CSS without breaking WordPress

Performance Optimization

September 7, 2026

You install a performance plugin. You flip every toggle. The score goes up. Then the mobile menu opens to a blank panel, the homepage slider stays empty until someone moves the mouse, and the checkout button does nothing on the first tap.

That pattern is so common it almost feels like a rite of passage. The win was real. Unused CSS and delayed scripts are some of the biggest levers you have for Core Web Vitals. The breakage was also real, because those same levers remove or postpone the code that makes interactive UI work.

This guide is the safer path. You will learn what defer and delay actually do (they are not the same), how remove unused CSS finds "unused" styles that only appear after a click, and the order that keeps menus, modals, and sliders alive while you still chase LCP, INP, and CLS. Everything here is grounded in how Lightify handles Asset Optimizations and its recommended quickstart. No mystery toggles. No "turn it all on and pray."

If you want the broader CWV picture after this article, pair it with our guide on how to fix LCP, CLS, and INP on WordPress in 2026. And if someone told you a cache plugin alone will fix everything, that myth is covered here: cache plugins do not fix Core Web Vitals.

Why people break their site with "one more optimization"

Most WordPress performance stacks mix three ideas that sound similar and behave very differently:

  1. Minify CSS and JavaScript (strip whitespace and comments).
  2. Defer scripts so they run after HTML parsing.
  3. Delay scripts until the visitor interacts (move, scroll, tap).
  4. Remove unused CSS so the first paint only carries styles for what is on screen.

Minify is usually boring and safe. Defer is moderate risk and great for your own scripts. Delay and unused CSS are the big wins and the big footguns. They change *when* code runs, or *whether* styles load before a click. Menus, off-canvas drawers, modals, tabs, sticky headers, and sliders live in that "after click" world. To a scanner, they look unused. To a visitor, they look broken.

Lightify separates these controls on purpose. Minify CSS/JS/HTML sits in Asset Optimizations. Defer or delay JS is explicit. Remove unused CSS is a Pro feature you turn on carefully. Speculative loading, lazy load, preload for the LCP/hero image, and the rest of the suite stay available, but you do not need them all on day one.

Defer vs delay: stop treating them as synonyms

This is the single most important distinction in the article.

Defer JavaScript

Defer means the browser finishes parsing HTML first, then runs the script. The script still loads as part of the normal page flow. It does not wait for a mouse move.

  • Risk level: moderate.
  • Best for: your own theme and plugin scripts that are not needed to paint the first screen, but *are* needed soon after.
  • What it fixes: parser-blocking scripts that inflate TTI and hurt INP without buying you much for first paint.

If a script builds a visible slider on load, deferring it can still work if the markup and CSS are ready and the script runs promptly after parse. Delaying that same script is a different story.

Delay JavaScript (Pro)

Delay holds the script until the visitor moves, scrolls, or taps. Nothing runs until there is interaction.

  • Risk level: higher.
  • Best for: third-party tags that do not paint your UI. Analytics, chat widgets, ad scripts, heatmaps, marketing pixels.
  • What it must never touch: anything that renders visible content on first paint or right after. A delayed slider script means an empty slider until the mouse moves. A delayed menu script means a hamburger icon that does nothing until scroll.

In Lightify, delay is a Pro control for a reason. Use it where the tradeoff is "this tag can wait," not "this UI can wait."

A practical split that survives real sites

| Script type | Prefer | Avoid | |—|—|—| | Theme/plugin scripts for UI you own | Defer + exclusions as needed | Delay | | Analytics, chat, ads, heatmaps | Delay (Pro) | Forcing them to run early | | Slider / carousel that paints above the fold | Defer carefully, or leave alone | Delay | | Consent managers / payment SDKs | Leave alone; do not self-host | Aggressive delay or self-host that breaks updates |

That table alone prevents most "my menu died" tickets.

Remove unused CSS without deleting your mobile menu

Remove unused CSS (Pro) is often the largest CSS win in the stack. The idea is simple: load CSS needed for the initial view, and defer the rest until interaction.

The failure mode is just as simple. Anything hidden until a click looks unused:

  • .mobile-menu / off-canvas drawers
  • .modal / lightbox overlays
  • .is-open / .active state classes
  • Mega menu panels
  • Cart drawers and account popovers
  • Tab panels that start display:none

The scanner is not wrong in a narrow sense. Those rules were not needed for the first paint. The visitor is also not wrong. They clicked the hamburger and got an unstyled mess.

The repair pattern: CSS inclusions

When something breaks after you enable remove unused CSS, do not turn the whole feature off. Add CSS inclusions for the selectors and files that belong to interactive UI. Typical targets:

  • Mobile menu wrappers and toggle classes
  • Modal / dialog containers
  • State classes like is-open, is-active, menu-open
  • Theme-specific drawer and sticky header classes

Think in patterns, not panic. "Hidden until click" is almost always an inclusion candidate. Purge after each change so you are not debugging a mix of old and new CSS.

Why purge after every change matters

Lightify includes full-page cache with gzip, preload/warm from sitemap, and purge on publish. Cart, checkout, and account pages are auto-excluded, which keeps WooCommerce dynamic pages out of the page cache. That is good. It also means you can accidentally test an optimized page while an older cached version still serves somewhere.

Know two actions cold:

  • Purge everything when you change global Asset Optimizations.
  • Purge current when you are iterating on one template.

If you skip purge, you will "fix" a menu, refresh, and still see the broken version. Then you will disable unused CSS forever and leave free performance on the table.

The safe order (Lightify quickstart, not folklore)

Flip toggles in this order. One change, then check the front end on desktop and mobile, then purge.

1. Confirm caching is working (and you are not stacking caches)

Do not run two caching plugins. Deactivate WP Rocket, W3 Total Cache, LiteSpeed Cache, or similar before you rely on Lightify's full-page cache. Lightify works fine with Cloudflare, Bunny, and managed hosts. Page cache and CDN are complementary when roles are clear. Two page caches fighting each other are not.

Confirm cart, checkout, and account stay excluded. That is already Lightify's default behavior for WooCommerce-style dynamic pages.

2. Keep the safe defaults, then touch HTML minify

Minify CSS/JS is on by default and mostly safe. Self-host CSS/JS (Pro) is also on by default. Leave payment SDKs and consent managers alone. Those must update from the vendor. Self-hosting them can freeze a security or compliance fix.

HTML minify is a reasonable early step. It is not where menus usually die.

3. Consider Prevent Layout Shifts carefully

Prevent Layout Shifts hides content until it is parsed. That can improve CLS and push LCP later. It is a tradeoff, not a free win. If your LCP element is text or a hero that appears late with this on, measure before you keep it.

4. Defer JavaScript, then add exclusions

Turn on defer for your own scripts. Browse the site. Open the mobile menu. Run the slider. Add to cart. If something fails, exclude that handle or file. Exclusions beat turning defer off sitewide.

5. Only then touch Pro levers, one at a time

This is where people usually go wrong. They enable unused CSS, delay JS, lazy render, and auto image WebP in one save. When the homepage breaks, they have no idea which toggle did it.

Enable in this sequence:

  1. Remove unused CSS → test menus/modals → add inclusions → purge.
  2. Delay JavaScript → only for third-party tags → never for visible UI scripts → purge.
  3. Lazy render (Pro) → useful, but it can break anchor links and long pages if a target section is not rendered yet. Test deep links.
  4. Auto image WebP → after the JS/CSS stack is stable.
  5. Preload critical / LCP / hero image once you know which image actually drives LCP.
  6. Database cleanup (revisions, spam, transients) only after a backup. Lightify can count first so you see what you are deleting.

Self-host third-party assets, font preload, font-display swap, and self-host Google Fonts remain available when you are ready. Speculative loading (prefetch on hover/focus/touch, plus Speculation Rules API) helps next-navigation feel instant without rewriting your CSS strategy. Bloat removal (emoji, dashicons where unused, jQuery Migrate, oEmbed, Heartbeat, XML-RPC) is another late pass once the critical path is clean.

Lazy load for images, video, and iframes, plus YouTube preview placeholders, belong in the media pass, not in the same breath as delay JS.

Exclusions over disabling: the rule that saves your weekend

When something breaks, your instinct is to disable the feature. That instinct costs you the win.

Prefer this ladder:

  1. Identify the broken UI (menu, modal, slider, sticky bar).
  2. Exclude the specific script, or include the specific CSS.
  3. Purge everything.
  4. Retest on a phone and a desktop.
  5. Only if the feature is still unstable after targeted fixes, turn that one feature off.

This is also why Lightify's real-visitor CWV dashboard matters. Lab scores lie politely. Field data for LCP, INP, CLS, FCP, and TTFB tells you whether the exclusion you added actually helped people, not just PageSpeed.

A walkthrough you can copy on a staging site

Use a staging copy. Do not learn on production Friday night.

  1. Install and activate Lightify. Deactivate any other page cache plugin.
  2. Confirm full-page cache is on. Hit a blog post twice and confirm the second response is served from cache. Confirm cart/checkout/account are excluded if you run WooCommerce.
  3. Leave minify CSS/JS alone (defaults). Optionally enable HTML minify.
  4. Enable Defer JS. Test header, mobile menu, slider, forms. Add exclusions for anything that fails. Purge everything.
  5. Enable Remove unused CSS (Pro). Open mobile menu and any modal. If styles are missing, add CSS inclusions for those selectors. Purge. Repeat until interactive UI looks correct.
  6. Enable Delay JS (Pro) only for analytics/chat/ads/heatmaps. Do not delay slider or menu scripts. Purge. Confirm third-party tags still fire after first interaction.
  7. Optionally enable lazy render, then test #section anchor links on long pages.
  8. Preload the real LCP/hero image. Enable lazy load for below-the-fold media. Add YouTube placeholders if you embed a lot of video.
  9. Watch the CWV dashboard from real visitors for a few days before you call it done.

If you want the full checklist beyond assets (hosting, images, fonts, database), use how to speed up your WordPress site in 2026 as the companion pass.

Common breakages and the fix that keeps the feature on

Mobile menu opens but looks unstyled

Cause: remove unused CSS treated off-canvas rules as unused. Fix: CSS inclusions for the menu wrapper, toggle, and open-state classes. Purge. Do not disable unused CSS sitewide.

Slider is empty until the mouse moves

Cause: the slider script was delayed. Fix: remove delay from that script. Defer may still be fine. Delay is for tags, not carousels.

Add to cart or checkout acts weird

Cause: another cache plugin still active, or a script required for WooCommerce got delayed. Fix: one cache plugin only. Keep dynamic pages excluded (Lightify already auto-excludes cart/checkout/account). Exclude or undelay checkout scripts.

Jump links on a long landing page do nothing

Cause: lazy render has not rendered the target yet. Fix: exclude those sections from lazy render, or disable lazy render on that template.

Consent banner or payment form fails after "self-host everything"

Cause: self-hosted a SDK that must stay on the vendor domain for updates. Fix: do not self-host payment SDKs or consent managers. Leave those requests alone.

Where Lightify fits (and where Pro membership fits)

Lightify is the all-in-one performance layer for this workflow: real-visitor CWV in the dashboard, full-page cache with gzip and sitemap warm/preload, Asset Optimizations (minify, defer/delay JS, remove unused CSS, self-host assets), lazy load, LCP preload, speculative loading, font controls, bloat removal, and database cleanup with a count-first step.

You do not need five overlapping plugins to defer JavaScript and remove unused CSS without breaking WordPress. You need one stack that separates defer from delay, gives you inclusions and exclusions, and teaches a quickstart order instead of a wall of toggles.

Pro features in this article (delay JS, remove unused CSS, self-host CSS/JS, lazy render, and more) ship through the DaftPlug Pro membership. One Pro license unlocks Lightify plus Progressify, Generatify, Rankify, and Snapshotify. New Pro plugins join existing memberships. Plans are Pro $37.99 (1 site), Pro Plus $74.99 (5 sites), and Pro Max $138.99 (100 sites), with a 14-day money-back guarantee. Features are the same across plans. Only site count changes. See pricing when you are ready.

Free habits still apply either way: purge after each change, exclusions over disabling, delay only third-party tags, and never stack two page caches.

Checklist you can paste into your runbook

  • [ ] Only one page cache plugin active
  • [ ] Cart / checkout / account confirmed excluded
  • [ ] Minify left on (defaults)
  • [ ] Payment SDKs and consent managers not self-hosted
  • [ ] Defer JS enabled; broken handles excluded
  • [ ] Remove unused CSS enabled; menu/modal selectors included
  • [ ] Delay JS limited to analytics/chat/ads/heatmaps
  • [ ] Lazy render tested against anchor links
  • [ ] LCP/hero image preloaded
  • [ ] Purge everything after each change
  • [ ] Field CWV (LCP/INP/CLS) checked after a few days of traffic

Bottom line

Defer and delay are different tools. Unused CSS is a win that needs inclusions for anything hidden until click. The safe order is boring on purpose: cache hygiene, defaults, defer with exclusions, then Pro levers one at a time with a purge between each step.

Do that, and you get the score without the blank slider and the ghost hamburger menu. That is the whole job.

Start with Lightify, keep pricing handy if you need Delay JS and Remove Unused CSS, and measure with real visitors instead of a single lab run. Your menus will thank you.