<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	xmlns:media="http://search.yahoo.com/mrss/" >

<channel>
	<title>Site Speed &#8211; Macronimous Blog</title>
	<atom:link href="https://www.macronimous.com/blog/category/site-speed/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.macronimous.com/blog</link>
	<description>Web design, web programming, Mobile apps, Opensource , SEO etc</description>
	<lastBuildDate>Wed, 16 Sep 2026 06:47:20 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1.2</generator>
	<item>
		<title>Our WordPress Admin Took 13 Seconds a Page. Here&#8217;s the Audit.</title>
		<link>https://www.macronimous.com/blog/wordpress-admin-slow-read-only-audit-case-study/</link>
					<comments>https://www.macronimous.com/blog/wordpress-admin-slow-read-only-audit-case-study/#respond</comments>
		
		<dc:creator><![CDATA[Jeffy Susan]]></dc:creator>
		<pubDate>Sun, 20 Sep 2026 12:18:41 +0000</pubDate>
				<category><![CDATA[CMS]]></category>
		<category><![CDATA[Content Management Systems]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[Site Speed]]></category>
		<category><![CDATA[Web Development]]></category>
		<category><![CDATA[WordPress Development]]></category>
		<category><![CDATA[Auditra]]></category>
		<category><![CDATA[Cloudflare]]></category>
		<category><![CDATA[MCP]]></category>
		<category><![CDATA[MCP Server]]></category>
		<category><![CDATA[WordPress performance]]></category>
		<category><![CDATA[WP-Cron]]></category>
		<guid isPermaLink="false">https://www.macronimous.com/blog/?p=5324</guid>

					<description><![CDATA[<p>A slow WordPress admin is usually blamed on plugin count, and plugin count is usually only part of it. On our own site the real chain was a six-process PHP-FPM pool on shared hosting, 2,800 daily wp-cron hits, 6,700 daily bot 404s, and two plugins waiting seven seconds on dead remote servers. A read-only WordPress [&#8230;]</p>
<p>The post <a rel="nofollow" href="https://www.macronimous.com/blog/wordpress-admin-slow-read-only-audit-case-study/">Our WordPress Admin Took 13 Seconds a Page. Here&#8217;s the Audit.</a> first appeared on <a rel="nofollow" href="https://www.macronimous.com/blog">Macronimous Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[<a href="https://www.macronimous.com/blog/wp-content/uploads/2026/09/Our-WordPress-Admin-Took-13-Seconds-a-Page.jpg"><img fetchpriority="high" decoding="async" width="1672" height="940" class="aligncenter size-full wp-image-5355" src="https://www.macronimous.com/blog/wp-content/uploads/2026/09/Our-WordPress-Admin-Took-13-Seconds-a-Page.jpg" alt="Slow WordPress admin Troubleshooting with MCP" /></a>
<div class="mac-direct-answer">
<strong>A slow <a href="https://www.macronimous.com/blog/wordpress-7-0-ai-the-token-cost-reality-for-site-owners/">WordPress</a> admin</strong> is usually blamed on plugin count, and plugin count is usually only part of it. On our own site the real chain was a six-process PHP-FPM pool on shared hosting, 2,800 daily wp-cron hits, 6,700 daily bot 404s, and two plugins waiting seven seconds on dead remote servers. A read-only WordPress <a href="https://www.macronimous.com/blog/building-a-wordpress-mcp-server-when-the-spec-changed/">MCP</a> server found the first problem in a minute and the last one needed a request profiler. This is what each tool could and couldn&#8217;t see.</div>
<div class="mac-toc">
<p class="mac-toc-title">In this post</p>
<ul>
<li><a href="#what-happened">What happened on a Tuesday</a></li>
<li><a href="#where-to-look">Why is the admin slow when the front end is fine? Where to look, in order</a></li>
<li><a href="#autoload">The 12 MB autoload: a real problem, and the wrong one</a></li>
<li><a href="#where-the-audit-stops">Where the audit stops</a></li>
<li><a href="#php-fpm-workers">Six PHP-FPM workers: the max_children limit on shared hosting</a></li>
<li><a href="#wp-cron-and-bots">WP-Cron and bot 404s were eating the pool</a></li>
<li><a href="#last-seven-seconds">The last seven seconds</a></li>
<li><a href="#what-we-changed">What we changed</a></li>
<li><a href="#what-audit-is-for">What a read-only WordPress MCP server is actually for</a></li>
</ul>
</div>
<h2 id="what-happened">What happened on a Tuesday</h2>
<p>On the 8th of September, macronimous.com&#8217;s admin went from sluggish to unusable. Opening Pages took long enough to make coffee. By the Wednesday, visitors were getting Cloudflare&#8217;s <a href="https://developers.cloudflare.com/support/troubleshooting/http-status-codes/cloudflare-5xx-errors/error-524/" target="_blank" rel="noopener noreferrer">524 timeout page</a> on the front end, which means Cloudflare reached our server, asked for the page, and gave up waiting after a hundred seconds. The site has been on the same WordPress install since 2015, it has 54 active plugins (we know), and it shares an InMotion account with our blog, which is a second, separate install with 33 plugins of its own.</p>
<p>We&#8217;d published <a href="https://wordpress.org/plugins/auditra/" target="_blank" rel="noopener noreferrer">Auditra</a> three weeks earlier, a free, read-only MCP server for WordPress that lets an <a href="https://www.macronimous.com/blog/the-code-your-ai-wants-to-delete-is-load-bearing/">AI</a> client read a site&#8217;s plugin inventory, autoload, cron schedule, database tables and known vulnerabilities. So the obvious move was to point it at our own site and see how far it got. This post is the honest version of that: what it found, where it stopped, and what it took to get the rest of the way.</p>
<p>We&#8217;ll warn you now, it runs long, because the interesting part is the sequence of wrong turns and not the final fix.</p>
<h2 id="where-to-look">Why is the admin slow when the front end is fine? Where to look, in order</h2>
<p>If you&#8217;re here because your own admin is crawling and you&#8217;d rather skip the story, this is the order we&#8217;d check now, having done it the long way.</p>
<ol>
<li><strong>cPanel ? Resource Usage.</strong> If CPU and memory are low, faults are zero, and Entry Processes reads 0 while the site is timing out, your PHP workers are running outside your account and you can&#8217;t see them. That&#8217;s a hosting question, not a plugin question.</li>
<li><strong>Bypass Cloudflare.</strong> Point the domain at the origin IP in your hosts file and hit the login page with <code>curl</code>. If it hangs direct, Cloudflare is cleared.</li>
<li><strong>Hit a bare <code>phpinfo.php</code>.</strong> If a one-line PHP file hangs, WordPress is cleared too.</li>
<li><strong>Ask the host two things:</strong> the PHP-FPM <code>max_children</code> for your pool, and a one-day count of requests to <code>wp-cron.php</code>, 404s, and top user agents.</li>
<li><strong>Read the autoload, cron list, and plugin inventory</strong> with a read-only audit (Auditra, WP-CLI, or by hand). Fix what&#8217;s obviously wrong, but don&#8217;t assume it&#8217;s the cause.</li>
<li><strong>Install Query Monitor</strong>, open an admin page, and read the HTTP API Calls tab before anything else. Dead remote calls hide there.</li>
</ol>
<p>Now the long way.</p>
<h2 id="autoload">The 12 MB autoload: a real problem, and the wrong one</h2>
<p>The first call was <code>analyze_autoload</code>. Autoloaded options are the rows in <code>wp_options</code> that WordPress pulls out of the database on every single request, admin or not, before a plugin has done anything. A healthy site keeps that under a megabyte. Ours was 12.7 MB across 989 options, and 98% of it was unattributed, which is the tool&#8217;s way of saying no active plugin claimed ownership of those rows.</p>
<p>The largest entries were all <code>_transient_string-locator-search-files-N</code>, numbered past 200, at 77 to 90 KB each. String Locator is a search-and-replace plugin we&#8217;d used and then deactivated, and it never cleaned up after itself. Every one of those transients had no expiry, and every one of them was autoloaded. We ran one <code>DELETE</code> in phpMyAdmin (206 rows, 0.08 seconds), called <code>analyze_autoload</code> again, and watched the number drop to 730 KB.</p>
<figure class="wp-caption alignnone"><img decoding="async" class="size-full wp-image-5346" src="https://www.macronimous.com/blog/wp-content/uploads/2026/09/audit-autoload-finding-12mb.png" alt="The audit result: 12.7 MB autoload, almost all of it leftover String Locator search transients, and the one-line fix" width="1400" height="852" /><figcaption class="wp-caption-text">The first result, one minute in. Correct finding, wrong diagnosis.</figcaption></figure>
<p>The first response from the model that read the audit opened with &#8220;Found it.&#8221; We&#8217;ve left that line in because it was wrong, and because it&#8217;s the most useful wrong answer in the whole story. A 12 MB autoload is a genuine defect that costs every request on the site, and fixing it changed nothing we could feel. The admin was exactly as slow afterwards. So the finding was correct and the diagnosis was not, and that distinction is the whole point of a tool that reports facts instead of verdicts.</p>
<h2 id="where-the-audit-stops">Where the audit stops</h2>
<p>Auditra reads state. It can&#8217;t time a request, and it can&#8217;t see anything below WordPress. So from here the work moved to things it has no access to: cPanel&#8217;s resource graphs, a shell, and the hosting company.</p>
<figure class="wp-caption alignnone"><img decoding="async" class="size-full wp-image-5350" src="https://www.macronimous.com/blog/wp-content/uploads/2026/09/audit-cannot-time-requests-next-steps.png" alt="Where the audit stops: it cannot time a request, so the next steps move to cPanel and cron" width="1400" height="647" /><figcaption class="wp-caption-text">The hand-off point. A read-only audit reads state; timing a request is a different tool.</figcaption></figure>
<p>The cPanel numbers made no sense at first. CPU sat at 9 to 15% of the limit, memory under 250 MB of 4 GB, zero faults on every hourly row, and Entry Processes (the count of web requests being handled) at zero while the site was timing out. At one point CPU read 215% with Entry Processes still at zero and six processes total, which is not what a busy website looks like. A <code>ps aux</code> from the cPanel terminal during a hang showed nothing running under our user at all.</p>
<figure class="wp-caption alignnone"><img loading="lazy" decoding="async" class="size-full wp-image-5348" src="https://www.macronimous.com/blog/wp-content/uploads/2026/09/cpanel-current-usage-215-cpu-zero-entry-processes.png" alt="cPanel Current Usage during the hang: 215% CPU, zero Entry Processes, six processes, no faults" width="1400" height="648" /><figcaption class="wp-caption-text">cPanel during a hang. Two cores busy, zero web requests being handled, six processes.</figcaption></figure>
<figure class="wp-caption alignnone"><img loading="lazy" decoding="async" class="size-full wp-image-5349" src="https://www.macronimous.com/blog/wp-content/uploads/2026/09/reading-cpanel-numbers-workers-outside-account.png" alt="Reading the cPanel numbers: CPU busy with zero entry processes means the work is happening outside the account" width="1400" height="637" /><figcaption class="wp-caption-text">The reading at the time: the work is happening somewhere the account can&#8217;t see. Right instinct, wrong layer.</figcaption></figure>
<p>To rule out Cloudflare we edited the hosts file on a Mac to point the domain straight at the InMotion IP and ran <code>curl</code> against the origin. The home page came back in 1.4 seconds (a cached file served by WP Fastest Cache from disk, no PHP involved). The login page hung indefinitely. So did <code>phpinfo.php</code>, a one-line file with no WordPress in it. That was the moment the site stopped being the suspect. Nothing in WordPress can make a bare <code>phpinfo()</code> hang.</p>
<p>We read all of that, at the time, as &#8220;requests aren&#8217;t reaching PHP.&#8221; That turned out to be the wrong layer too, and we&#8217;ll own it: the requests were inside PHP, in a place we couldn&#8217;t see.</p>
<h2 id="php-fpm-workers">Six PHP-FPM workers: the max_children limit on shared hosting</h2>
<p>The InMotion engineer (a patient man, by the third hour) found it: the PHP-FPM pool for the account was hitting its <code>max_children</code> limit, and that limit was six. Six PHP processes for a 54-plugin admin, a 33-plugin blog, all their cron jobs, and every bot on the internet. And the number is fixed per plan on shared hosting; it can&#8217;t be raised, and there&#8217;s no slow log to tell you what&#8217;s holding the slots.</p>
<p>In hindsight every odd number fit. The pool runs as a server user outside the cPanel account, which is why <code>ps</code> showed nothing and Entry Processes read zero. The six processes and the 215% CPU on Tuesday were the six workers, all busy. And the front page kept loading because a cached HTML file never asks the pool for anything.</p>
<div class="mac-key-point">
<p>On shared hosting, the number that decides whether your admin works is one you can&#8217;t see in WordPress, can&#8217;t see in cPanel, and can&#8217;t change. Ask your host what it is before you trust any other diagnosis.</p>
</div>
<h2 id="wp-cron-and-bots">WP-Cron and bot 404s were eating the pool</h2>
<p>What was filling six slots all day? The same engineer pulled a day of access logs, and this is where the audit&#8217;s cron finding from the first hour turned out to matter after all.</p>
<table class="styled-table">
<thead>
<tr>
<th>Request type (one day)</th>
<th>Count</th>
<th>Cost</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>POST /blog/wp-cron.php</code></td>
<td>2,086</td>
<td>Full WordPress load each, one worker held</td>
</tr>
<tr>
<td><code>POST /wp-cron.php</code> (main site)</td>
<td>701</td>
<td>Same</td>
</tr>
<tr>
<td>404s under <code>/blog/</code></td>
<td>6,758</td>
<td>Uncached, full WordPress load each</td>
</tr>
<tr>
<td>WP Fastest Cache preload bot</td>
<td>823</td>
<td>Our own plugin, hitting our own site</td>
</tr>
<tr>
<td>AwarioBot + a webmeup scraper</td>
<td>1,400</td>
<td>Nothing we wanted</td>
</tr>
</tbody>
</table>
<p>WordPress has no clock. Its scheduled jobs run when a visitor&#8217;s page load notices one is due and spawns an extra request to <code>wp-cron.php</code>. With bots hitting the site a few thousand times a day, that&#8217;s a few thousand cron spawns, each one taking a worker for a full WordPress boot. Auditra had listed 65 cron events on the main site, one of them (a management connector we&#8217;d installed and never used) firing every minute. We&#8217;d noted it as &#8220;secondary&#8221; on day one. It wasn&#8217;t secondary. It was a sixth of the pool, permanently.</p>
<figure class="wp-caption alignnone"><img loading="lazy" decoding="async" class="size-full wp-image-5347" src="https://www.macronimous.com/blog/wp-content/uploads/2026/09/audit-secondary-findings-cron-tables.png" alt="Secondary findings from the same audit: 65 cron events, heavy security log tables, no object cache" width="1400" height="452" /><figcaption class="wp-caption-text">The &#8220;secondary&#8221; findings from day one. The cron line turned out to be the main event.</figcaption></figure>
<p>The 404s were ordinary background noise, scanners probing for install.php and fake plugin paths, no single source worth blocking. The problem was only that each one cost a worker, because a 404 page is never cached.</p>
<h2 id="last-seven-seconds">The last seven seconds</h2>
<p>After the cron and bot traffic was dealt with (details below), the admin was still slow, and this is where a read-only audit has nothing left to offer. You need something that times a live request. We installed <a href="https://wordpress.org/plugins/query-monitor/" target="_blank" rel="noopener noreferrer">Query Monitor</a>, opened the Pages list, and read the panel: 13.2 seconds page generation, 569 database queries, and an HTTP API tab that told the rest of the story in four lines.</p>
<figure class="wp-caption alignnone"><img loading="lazy" decoding="async" class="size-full wp-image-5351" src="https://www.macronimous.com/blog/wp-content/uploads/2026/09/query-monitor-timeline-569-queries.png" alt="Query Monitor timeline for the Pages list: 569 database queries, two PHP errors, 13.2 seconds page generation" width="1400" height="414" /><figcaption class="wp-caption-text">Query Monitor on the Pages list: 569 queries, 13.2 seconds, and an HTTP API tab worth more than all of them.</figcaption></figure>
<p>Simple Job Board, which ran our careers page, was calling its vendor&#8217;s add-on marketplace on every admin page load to check for updates. That server was stuck in a redirect loop, so each call ran 2.8 seconds before giving up, and it made the call twice. Five and a half seconds per admin page, holding a worker the whole time, for a marketplace we&#8217;d never bought anything from. CrawlWP, an indexing plugin, was trying to refresh a Yandex Webmaster token twice per page and getting a 400 each time, another 1.2 seconds, for a search engine none of our clients use.</p>
<p>We deactivated both. Page generation went from 13.2 seconds to 1.37.</p>
<p>Neither plugin was flagged by anything. Both were up to date, both were actively maintained, both had clean vulnerability records. An inventory tool can&#8217;t see that a plugin&#8217;s phone-home endpoint died last month. Only a profiler watching the request can, and only when you happen to look.</p>
<h2 id="what-we-changed">What we changed</h2>
<p>For anyone in the same situation, this is the list, in the order we did it. Most of it is in the <a href="https://developer.wordpress.org/advanced-administration/wordpress/wp-config/" target="_blank" rel="noopener noreferrer">wp-config reference</a> and Cloudflare&#8217;s <a href="https://developers.cloudflare.com/cache/how-to/cache-rules/" target="_blank" rel="noopener noreferrer">cache rules docs</a> if you want the full detail.</p>
<ul class="mac-checklist">
<li>Deleted 206 orphaned String Locator transients (12.7 MB autoload to 730 KB)</li>
<li>Removed the every-minute management connector, and confirmed via <code>analyze_cron</code> that it left no orphaned events</li>
<li><code>define('DISABLE_WP_CRON', true);</code> on both installs, with two cPanel server crons at 5 and 15 minutes replacing 2,800 daily visitor-triggered hits with about 380</li>
<li>Cloudflare cache rule: 404 responses under <code>/blog/</code> cached at the edge for an hour, so repeat scans never reach PHP</li>
<li>Cloudflare WAF rules blocking the two scrapers by user agent, and the usual scanner paths (<code>install.php</code>, <code>xmlrpc.php</code>, <code>.env</code>)</li>
<li>A physical <code>robots.txt</code> for the blog, because WordPress only serves its virtual one at the domain root</li>
<li>WP Fastest Cache preload throttled to 4 pages a minute</li>
<li>13 inactive plugins deleted, 8 redundant active ones removed, 2 deactivated (down to about 42 active)</li>
<li>Simple Job Board and CrawlWP deactivated; the careers page became a paragraph of text</li>
</ul>
<p>Two things we didn&#8217;t fix, for the record. The theme depends on Titan Framework, which has an unfixed medium-severity CVE from 2021 and has been closed on wordpress.org for years; replacing it is a theme rework, not a Tuesday job. And the blog&#8217;s 33 plugins haven&#8217;t been audited yet. If you&#8217;ve read our post on <a href="https://www.macronimous.com/blog/hidden-technical-debt-wordpress-seo/">technical debt in old WordPress installs</a>, you already know how this goes: you never finish, you just get to the point where the site stops fighting you.</p>
<h2 id="what-audit-is-for">What a read-only WordPress MCP server is actually for</h2>
<p>We built Auditra to be read-only on purpose, and we wrote up why in the <a href="https://www.macronimous.com/blog/wordpress-plugin-security-visibility/">plugin security post</a>: no write calls, no scores, facts with thresholds next to them, and the reasoning left to whoever (or whatever) reads the output. This week was the first real test of that split on a site we actually cared about, and here&#8217;s our verdict.</p>
<p>It did the first hour and the inventory. Autoload, cron, table sizes, the plugin list with vulnerability flags and content usage, all in a few calls, with none of the clicking through fifty settings screens that a plugin cleanup usually means. And it did the verification: after every change, one call confirmed the cron flag was set, the connector was gone, the server cron had fired. That&#8217;s the part people underrate. Knowing a fix took is worth as much as the fix.</p>
<p>It could not diagnose a live hang, and we don&#8217;t think a read-only tool should pretend to. That took cPanel graphs, a hosts-file bypass, the host&#8217;s view of a pool we couldn&#8217;t see, and a profiler timing a real request. Four tools, none of them the audit. If we&#8217;d sold Auditra as &#8220;finds your performance problem,&#8221; it would have failed this week, publicly, on our own site.</p>
<p>So the pitch we&#8217;d make to another senior WordPress developer is smaller than the one we started with, and we think it&#8217;s the right one. Every site you inherit has a history you can&#8217;t see from the admin. A read-only audit reads that history in a minute, in a form an AI client can reason over, and tells you what to rule out first. Ruling out is where the hours go on a bad day. It won&#8217;t tell you the pool has six workers. But it&#8217;ll get you to the question a lot faster, and it&#8217;ll confirm you fixed what you think you fixed.</p>
<p>That&#8217;s what we found on our own site. We&#8217;d be curious what a ten-year-old install of yours turns up, and if Auditra tells you something you didn&#8217;t know, or misses something it should have caught, the comments are open and we&#8217;ll add it with credit.</p>
<div class="mac-cta-box">
<h3>Run the same audit on a site you maintain</h3>
<p>Auditra is free, GPL, and read-only. Install it, connect any MCP client, and read your own site&#8217;s autoload, cron, tables and plugin history in a few calls. If what comes back is more than you want to deal with, <a href="https://www.macronimous.com/services/cms-development/wordpress-maintenance-services/">we&#8217;ve been cleaning up WordPress installs since 2008</a>.</p>
<p><a class="mac-cta-button" href="https://www.macronimous.com/free-tools/auditra/">Get Auditra and point it at your site</a></p>
</div>
<p>The post <a rel="nofollow" href="https://www.macronimous.com/blog/wordpress-admin-slow-read-only-audit-case-study/">Our WordPress Admin Took 13 Seconds a Page. Here&#8217;s the Audit.</a> first appeared on <a rel="nofollow" href="https://www.macronimous.com/blog">Macronimous Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.macronimous.com/blog/wordpress-admin-slow-read-only-audit-case-study/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Replace JavaScript with CSS: A Lightweight Approach to Faster Frontends</title>
		<link>https://www.macronimous.com/blog/replace-javascript-with-css-a-lightweight-approach-to-faster-frontends/</link>
					<comments>https://www.macronimous.com/blog/replace-javascript-with-css-a-lightweight-approach-to-faster-frontends/#respond</comments>
		
		<dc:creator><![CDATA[Benny]]></dc:creator>
		<pubDate>Tue, 02 Sep 2025 07:10:17 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Site Speed]]></category>
		<category><![CDATA[Website performance]]></category>
		<category><![CDATA[CSS]]></category>
		<category><![CDATA[fast loading websites]]></category>
		<category><![CDATA[javascript]]></category>
		<guid isPermaLink="false">https://www.macronimous.com/blog/?p=4908</guid>

					<description><![CDATA[<p>Modern websites often load megabytes of JavaScript just to run simple UI interactions. While JavaScript is essential for dynamic data and app logic, many interface patterns don’t actually need it. Overusing JS hurts page speed, makes sites harder to maintain, and can reduce accessibility. The good news is that modern HTML and CSS already give [&#8230;]</p>
<p>The post <a rel="nofollow" href="https://www.macronimous.com/blog/replace-javascript-with-css-a-lightweight-approach-to-faster-frontends/">Replace JavaScript with CSS: A Lightweight Approach to Faster Frontends</a> first appeared on <a rel="nofollow" href="https://www.macronimous.com/blog">Macronimous Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[<a href="https://www.macronimous.com/blog/wp-content/uploads/2025/09/Replace-JavaScript-with-CSS.png"><img loading="lazy" decoding="async" width="1024" height="683" class="aligncenter size-large wp-image-4909" src="https://www.macronimous.com/blog/wp-content/uploads/2025/09/Replace-JavaScript-with-CSS-1024x683.png" alt="Replace JavaScript with CSS" /></a>
<p>Modern websites often load megabytes of JavaScript just to run simple UI interactions. While JavaScript is essential for dynamic data and app logic, many interface patterns don’t actually need it. Overusing JS hurts page speed, makes sites harder to maintain, and can reduce accessibility. The good news is that modern HTML and CSS already give us solutions for many common interactions.</p>
<p>In this post, you’ll see how to replace heavy JavaScript snippets with lightweight CSS and HTML equivalents. These patterns are copy-paste ready, improve performance, and simplify your frontend code.</p>
<ol>
<li><strong> Accordions with </strong><strong>&lt;details&gt;</strong><strong>and </strong><strong>&lt;summary&gt;</strong></li>
</ol>
<p>Most “FAQ” sections or toggle accordions are built with JavaScript click handlers. But HTML provides this natively:</p><pre class="urvanov-syntax-highlighter-plain-tag">&lt;details&gt; 
  &lt;summary&gt;What’s included in the plan?&lt;/summary&gt; 
  &lt;p&gt;Nightly backups, plugin updates, and uptime monitoring.&lt;/p&gt; 
&lt;/details&gt;</pre><p>This works out of the box, supports keyboard navigation, and is screen-reader friendly.</p>
<ol start="2">
<li><strong> Tabs with radio buttons and CSS</strong></li>
</ol>
<p>Tabbed interfaces often rely on bulky JavaScript. A simple combination of radio inputs and CSS selectors can replace that:</p><pre class="urvanov-syntax-highlighter-plain-tag">&lt;div class="tabs"&gt;
  &lt;input type="radio" id="tab1" name="tabs" checked&gt;
  &lt;label for="tab1"&gt;Overview&lt;/label&gt;
  &lt;div class="panel"&gt;Overview content…&lt;/div&gt;

  &lt;input type="radio" id="tab2" name="tabs"&gt;
  &lt;label for="tab2"&gt;Features&lt;/label&gt;
  &lt;div class="panel"&gt;Features content…&lt;/div&gt;
&lt;/div&gt;

&lt;style&gt;
.tabs { display: grid; }
.panel { display: none; padding:1rem; border:1px solid #ccc; }
#tab1:checked ~ label[for="tab1"] + .panel,
#tab2:checked ~ label[for="tab2"] + .panel { display:block; }
&lt;/style&gt;</pre><p>&nbsp;</p>
<p>This is lightweight and works without any script.</p>
<ol start="3">
<li><strong> Dropdown menus with </strong><strong>:hover</strong><strong>and </strong><strong>:focus-within</strong></li>
</ol>
<p>Navigation menus are often powered by JS toggles, but CSS handles them too:</p><pre class="urvanov-syntax-highlighter-plain-tag">&lt;ul class="nav"&gt;
  &lt;li&gt;
    &lt;a href="#"&gt;Services&lt;/a&gt;
    &lt;ul class="submenu"&gt;
      &lt;li&gt;&lt;a href="#"&gt;WordPress&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href="#"&gt;Shopify&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;style&gt;
.nav { display:flex; gap:1rem; }
.nav li { position:relative; list-style:none; }
.submenu { display:none; position:absolute; background:#fff; border:1px solid #ccc; }
li:hover &gt; .submenu,
li:focus-within &gt; .submenu { display:block; }
&lt;/style&gt;</pre><p>&nbsp;</p>
<p>This works for both mouse and keyboard users.</p>
<ol start="4">
<li><strong> Modals with </strong><strong>:target</strong></li>
</ol>
<p>A lightweight modal without JS:</p><pre class="urvanov-syntax-highlighter-plain-tag">&lt;a href="#modal"&gt;Open modal&lt;/a&gt;
&lt;div id="modal"&gt;
  &lt;a href="#" class="overlay"&gt;&lt;/a&gt;
  &lt;div class="content"&gt;
    &lt;a href="#"&gt;Close&lt;/a&gt;
    &lt;p&gt;Hello from CSS-only modal!&lt;/p&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;style&gt;
#modal { display:none; position:fixed; inset:0; align-items:center; justify-content:center; }
#modal:target { display:flex; }
.overlay { position:absolute; inset:0; background:rgba(0,0,0,.5); }
.content { background:#fff; padding:1rem; border-radius:.5rem; }
&lt;/style&gt;</pre><p>&nbsp;</p>
<p>For full accessibility (focus trapping, ESC key closing), add a tiny JavaScript enhancement.</p>
<ol start="5">
<li><strong> Carousels with CSS scroll-snap</strong></li>
</ol>
<p>Instead of a heavy slider plugin, use modern CSS:</p><pre class="urvanov-syntax-highlighter-plain-tag">&lt;div class="slider"&gt;
  &lt;div class="slide"&gt;Item 1&lt;/div&gt;
  &lt;div class="slide"&gt;Item 2&lt;/div&gt;
  &lt;div class="slide"&gt;Item 3&lt;/div&gt;
&lt;/div&gt;

&lt;style&gt;
.slider { display:flex; overflow-x:auto; scroll-snap-type:x mandatory; gap:1rem; }
.slide { flex:0 0 80%; scroll-snap-align:start; border:1px solid #ccc; padding:1rem; }
&lt;/style&gt;</pre><p>&nbsp;</p>
<p>This is mobile-friendly and very smooth without JS.</p>
<ol start="6">
<li><strong> Form validation with HTML and CSS</strong></li>
</ol>
<p>Stop writing extra JavaScript validators when HTML already provides built-in checks:</p><pre class="urvanov-syntax-highlighter-plain-tag">&lt;form&gt;
  &lt;label&gt;Email &lt;input type="email" required&gt;&lt;/label&gt;
  &lt;button&gt;Submit&lt;/button&gt;
&lt;/form&gt;

&lt;style&gt;
input:required:invalid { border-color: red; }
input:required:valid { border-color: green; }
&lt;/style&gt;</pre><p>&nbsp;</p>
<p>Use attributes like required, pattern, min, and maxlength for free validation.</p>
<ol start="7">
<li><strong> Tooltips with CSS only</strong></li>
</ol>
<p>Simple hover tooltips:</p><pre class="urvanov-syntax-highlighter-plain-tag">&lt;button class="tip" data-tip="Runs nightly"&gt;Backups&lt;/button&gt;

&lt;style&gt;
.tip { position:relative; }
.tip[data-tip]::after {
  content: attr(data-tip);
  position:absolute; left:50%; transform:translateX(-50%);
  bottom:125%; background:#000; color:#fff; padding:.25rem .5rem;
  border-radius:.25rem; opacity:0; transition:.2s;
}
.tip:hover::after,
.tip:focus-visible::after { opacity:1; }
&lt;/style&gt;</pre><p>&nbsp;</p>
<ol start="8">
<li><strong> In-view animations with scroll-driven CSS</strong></li>
</ol>
<p>If you’re targeting modern browsers, scroll animations can be pure CSS:</p><pre class="urvanov-syntax-highlighter-plain-tag">@keyframes fade { from { opacity:0; transform:translateY(20px); } to { opacity:1; transform:none; } }
.reveal { animation: fade 1s linear both; animation-timeline: scroll(block); }</pre><p>No need for scroll event listeners.</p>
<p><strong>When You Still Need JavaScript</strong></p>
<p>CSS and HTML can do a lot, but JavaScript remains essential for:</p>
<ul>
<li>Fetching and rendering dynamic data</li>
<li>Managing complex state (shopping carts, dashboards)</li>
<li>Focus management for true accessibility</li>
<li>Rich interactivity (drag-drop, live charts, etc.)</li>
</ul>
<p>The goal isn’t to remove JavaScript completely — but to <strong>use it only where it matters</strong>.</p>
<p><strong>Conclusion</strong></p>
<p>By leaning on native HTML elements and CSS selectors, you can replace large chunks of frontend JavaScript. This leads to faster load times, simpler code, and better accessibility. Next time you reach for a script, ask yourself: <em>Can this be done with CSS instead?</em></p>
<p>Less JavaScript. More performance. Happier users.</p>
<p>The post <a rel="nofollow" href="https://www.macronimous.com/blog/replace-javascript-with-css-a-lightweight-approach-to-faster-frontends/">Replace JavaScript with CSS: A Lightweight Approach to Faster Frontends</a> first appeared on <a rel="nofollow" href="https://www.macronimous.com/blog">Macronimous Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.macronimous.com/blog/replace-javascript-with-css-a-lightweight-approach-to-faster-frontends/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
