Buyers always tries to win. But they don't understand in tech industry it's not as easy as it seems. And website performance optimization specialists know this very well.
You pick a theme because it looks stunning. Months later you're in DevTools at 2am, wondering why a hero section needs loading 400 KB of JavaScript to appear. I've been there — and it's not the theme's fault. It's the trade‑off nobody mentions: good‑looking themes are usually JS‑first, and JS‑first is fast to sell but expensive to optimize.
Here's what I found optimizing a real Uncode + WPBakery site, and how I got it to Mobile 85 / Desktop 95 without editing the theme.
The setup
Uncode is a premium theme; it looks great and the builder is pleasant. The performance stack was LiteSpeed Cache, Perfmatters, OMGF (fonts), and EWWW (images). Constraint: no core edits — only reversible snippets and settings.
The problem: below the fold was fine. Above the fold, every element — text, headings, logo, menu colors, hero height, background — was finalized by JavaScript. Defer that JS and the page looks broken.
What's actually inside these themes
Uncode ships a mini‑framework: a dozen or so JS modules (init, global, utils, animations, menuSystem, carousel, parallax, rotatingTxt, textMarquee, dropImage, stickyElements, onePage, magnetic, cursor) plus libraries (GSAP + ScrollTrigger, Waypoints + Inview, Owl Carousel, Rellax, MediaElement, LightGallery, Isotope). It adds state classes at runtime (start_animation, loaded-split-word, style-dark-override, is_stuck, data-revealed, owl-loaded). It computes section heights from window.innerHeight. And it fires a lot of events on load (scroll, resize, custom re‑layout events).
None of that is wrong. It's just that the HTML is a draft and JavaScript is the author.
The seven problems, and the fixes
1) Above the fold only rendered with JS.
Uncode computes hero height in JS, so deferring it collapsed the section. Fix: reserve the space in CSS and force visibility until the theme is ready.
#page-header .row[data-height-ratio="full"] { min-height: 100vh; min-height: 100svh; }
html.pre-interaction #row-unique-0 .animate_when_almost_visible { opacity:1 !important; animation:none !important; }
2) Remove Unused CSS ate the reveal rules.
This was the big one. RUCSS builds "used CSS" from the initial DOM; the classes the theme adds later (start_animation, loaded-split-word, owl-loaded) weren't there in used css that loads instantly to render above the fold, so their reveal rules were dropped. The page was hidden by CSS and had no rule left to show it. Fix: exclude every runtime class from unused‑CSS removal.
.start_animation .loaded-split-word .animate_when_almost_visible
.animate_when_parent_almost_visible .heading-line-wrap
.split-word .split-word-inner .split-char
.owl-loaded .owl-carousel .owl-stage .owl-item .owl-height-viewport
One exclusion list fixed the hero, the header, and the carousel.
3) The header depended on a JS‑added class.
The "white text over the hero" rules keyed on .style-dark-override, added after load, while the rules lived in a delayed stylesheet. Fix: copy the theme's exact transparent‑state rules into a small gate that doesn't wait on runtime classes.
(function () {
var GATE = 'pre-interaction';
var root = document.documentElement;
root.classList.add(GATE);
var released = false;
function release() {
if (released) return;
released = true;
setTimeout(function () { root.classList.remove(GATE); }, 250);
}
['mousemove', 'mousedown', 'pointerdown', 'touchstart', 'keydown', 'wheel', 'scroll']
.forEach(function (evt) {
window.addEventListener(evt, release, { once: true, passive: true });
});
function autoplay() {
var v = document.querySelector('#row-unique-0 video.background-video-shortcode');
if (!v || !v.play) return;
v.muted = true;
v.loop = true;
v.playsInline = true;
v.autoplay = true;
['autoplay', 'muted', 'loop', 'playsinline'].forEach(function (a) { v.setAttribute(a, a); });
try { v.play(); } catch (e) {}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', autoplay);
} else {
autoplay();
}
})();
4) Parallax was JS‑driven.
Rellax applied inline transforms and a 130% wrapper. Fix: neutralize it and use CSS.
#page-header .row-container.with-parallax > .background-wrapper {
transform:none !important; height:100% !important; will-change:auto !important;
}
#page-header .row-container.with-parallax .background-inner {
background-attachment: fixed !important; background-size: cover !important;
}
5) JS Deferral Caused Blank Hero Paints & Heavy Layout Shifts (CLS 0.19)
When JavaScript deferral or optimization plugins are enabled, the page initial render goes missing. Since the theme relies on JS to compute header sizes and inject video background markup, the initial HTML paint renders as a blank block with zero height.
Without JS: Background videos, image placeholders, section containers, and elements are completely absent from the initial render DOM
When JS Parses: The moment the main thread finally parses and executes the deferred JavaScript bundle, all missing hero elements suddenly render at once.
The Performance Penalty: This delayed injection forces a massive DOM reflow—causing LCP to balloon up to 4.30s and triggering severe layout instability with a CLS spike of 0.19.
The Takeaway: HTML alone acts merely as a skeletal outline; JavaScript acts as the actual page architect.
6) Delaying JS broke things two scripts away.
This is the one that humbles you. The carousel threw Waypoint.Inview is not a constructor because inview.min.js was delayed while carousel.min.js ran immediately. Then ScrollTrigger is not defined because GSAP was delayed while the carousel and the rotating hero text used it. The dependency graph is scattered across files and invisible until it explodes. Rule: never delay a library that a non‑delayed script consumes. Delay leaf features (lightbox, isotope, tabs, counters, forms, charts); keep shared libs on defer.
7) Lazy‑loading blanked the hero.
Above‑the‑fold backgrounds must not be lazy. Excluding the header container fixed it:
add_filter('perfmatters_lazyload_parent_exclusions', function ($ex) {
$ex[] = 'header-uncode-block'; return $ex;
});
The logged‑in mirage
One more gotcha: it looked fine to the admin. RUCSS and similar features only run for anonymous visitors. Test logged out, in incognito, or you'll debug the wrong page.
Results
- Above the fold renders without JS.
- CLS ≈ 0.
- No optimization‑induced console errors.
- PageSpeed: Mobile 85, Desktop 95, with zero core theme edits.
The takeaway
"Just install a cache plugin" doesn't work on JS‑first themes, because caching can't fix a render that depends on runtime classes. You're reverse‑engineering a framework: reading minified code and CSS to find why the paint is wrong, and making every fix reversible so the client can ship it. It's real engineering — and it deserves to be planned and priced as such.
Builders sell the look. Someone still has to pay for the paint.









Top comments (0)