Frontend
My Copy Buttons Vanished on Production. The Bug Was Three WordPress Layers Deep
shekhar chandran Dev.to (EN Zone)
2 views
I shipped a small feature: 49 "📋 Copy Prompt" buttons on a prompt-library page, each with an inline onclick that copies text to the clipboard. Worked perfectly in preview. Went live. Every single button vanished.
Not broken — gone. Not in the DOM. Not in "view source." Just... not there.
Here's how I traced it through three separate, unrelated bugs stacked on top of each other, and what I learned about WordPress content filters and caching layers that I wish I'd known before shipping.
Symptom #1: things were also just... centered wrong
Before I even got to the missing buttons, screenshots came in showing paragraphs and prompt boxes that should've been left-aligned rendering centered or oddly indented on certain line wraps. That one turned out to be simple: several wrapper divs and <p>/<h4> styles had no explicit text-align, and something in the theme/page-builder CSS cascade was centering them under specific conditions. Fix: add explicit text-align:left inline styles everywhere it mattered. Not interesting on its own — but it meant I almost stopped looking once I fixed the "obvious" visual bug, instead of noticing the buttons were missing entirely.
Symptom #2: the buttons weren't just styled wrong, they didn't exist
This is the one that took real digging. My working theory list, in order of how wrong each one was:
Cache serving stale content → ruled out (forced no-cache fetch, same result)
CSS display:none somewhere → ruled out (not in computed styles, because the elements weren't in the DOM at all)
A plugin stripping <button> tags → seemed unlikely but worth checking
The technique that actually cracked it was comparing the same content at three different layers:
What's stored in the database — clean, correct HTML, buttons present, onclick intact.
What's actually served over HTTP — fetched the live URL directly (bypassing any client-side rendering) and diffed it against #1.
What the browser's DOM ends up with — document.querySelectorAll('button') in devtools.
Layer 1 was fine. Layer 2 was corrupted. That narrowed it to something between "database" and "the HTTP response" — i.e., a content filter running at render time, not anything client-side.
Root cause: WordPress's own wptexturize() was corrupting my HTML attributes
WordPress runs wptexturize() on the_content at render time — it's the filter that turns straight quotes into "smart" curly quotes for readability in plain prose. The problem: it doesn't know the difference between quote characters in your visible text and quote characters inside an HTML attribute.
My button markup looked like this:
<button onclick="navigator.clipboard.writeText('...');this.textContent='✓ Copied';">
📋 Copy Prompt
</button>
wptexturize() converted some of the straight ' characters inside that onclick string into curly-quote HTML entities (’). That meant the served HTML had no real closing quote for the attribute value anymore. The browser's HTML parser, following spec, treated the <button> tag as an attribute that never terminated — and silently swallowed everything after it into that one unclosed tag, until the next literal <button> string in the markup forced it closed. 49 buttons, cascading into each other, all eaten by the parser. That's why they weren't just mis-styled — they'd never actually existed as separate elements in the parsed DOM at all.
Fix: never put inline onXXX="..." attributes with string literals containing quote characters into WordPress post/page content. I replaced all 49 buttons with a plain class and moved the behavior into one delegated listener:
<button class="copy-btn" data-text="the actual prompt text">
📋 Copy Prompt
</button>
<script data-no-optimize="1" data-cfasync="false">
document.querySelectorAll('.copy-btn').forEach(btn => {
btn.addEventListener('click', () => {
navigator.clipboard.writeText(btn.dataset.text);
btn.textContent = '✓ Copied';
});
});
</script>
Bug #3: the fix... also didn't work at first
Buttons were back in the DOM. Great. Except the click handler still didn't fire. Same three-layer trick: DB content had the <script> block, but the served HTML didn't.
This time it wasn't wptexturize — it was LiteSpeed Cache's JS minify/combine optimizer silently stripping the inline <script> block during minification. Confirmed via response headers: x-litespeed-cache: miss on a forced fresh render (so it wasn't a stale-cache problem) but x-litespeed-tag showed an active JS-minify hash — meaning the optimizer was actively processing and dropping the block, not just caching it.
Fix: LiteSpeed Cache respects a documented exclusion convention — add data-no-optimize="1" data-cfasync="false" to any inline <script> you need untouched (already in the snippet above). Script showed up in the served HTML immediately after that, and the click handler worked.
Takeaways if you're shipping anything similar on WordPress + LiteSpeed
Never use inline onXXX attributes with quoted string literals in post/page content. wptexturize() runs on the_content and can corrupt quote characters inside them without warning — it only cares about "readable prose," not your JS.
Prefer class + a single delegated event listener over per-element inline handlers. Cleaner, and immune to the above.
Any inline <script> in post content on a LiteSpeed-cached WordPress site needs data-no-optimize="1", or the JS minifier can silently strip it — no error, no warning, it's just gone from the served page.
When something works in the database/editor but not live, diff three layers: stored content → raw served HTTP response → parsed DOM. Whichever layer first shows the corruption tells you exactly which system to blame, instead of guessing across the whole stack.
Fixing the obvious visual bug can make you stop looking too early. The centering issue was real, but it wasn't the actual story — always check for a second, quieter bug hiding behind the loud one.
I ran into this while rebuilding a prompt-library page over on AIInsider.in — if you're curious what the finished (bug-free) version looks like, that's it in the wild.
Has anyone else been bitten by wptexturize mangling inline JS? Curious how common this actually is outside my one site.
Read original: https://dev.to/aiinsider/my-copy-buttons-vanished-on-production-the-bug-was-three-wordpress-layers-deep-203j
Related
Path-Based Routing with Reverse Proxy: Serving Multiple Websites from One Domain
Frontend
4
Dev.to (EN Zone)
Why your OpenGraph tags break on LinkedIn (and how to actually fix it)
Frontend
4
Dev.to (EN Zone)
The Need for a Modern UML and Diagram Engine (Part 2)
Frontend
6
DEV Community
[Showoff Saturday] A little SVG character that spills coffee and points at a button
Frontend
5
Reddit r/webdev
Comments0
No comments yet — be the first