How jekcms Hit a 97 Mobile PageSpeed Score on Shared Hosting

A real 97 mobile PageSpeed score on shared hosting: which jekcms mechanism moves LCP, CLS and TBT — and which numbers I refuse to invent.

How jekcms Hit a 97 Mobile PageSpeed Score on Shared Hosting

A real 97 mobile PageSpeed score on shared hosting: which jekcms mechanism moves LCP, CLS and TBT — and which numbers I refuse to invent.

One of the 14 content sites I run on jekcms tests at 97 on mobile PageSpeed. The setup is deliberately unimpressive: shared LiteSpeed hosting with Cloudflare in front. No VPS, no edge workers, nothing you'd brag about in a conference talk. If the CMS needed expensive infrastructure to be fast, I'd treat that as a bug — I wrote the software, and the hosting bill for fourteen sites lands on my desk.

A disclaimer before anything else. Performance case studies are where blogs usually start inventing milliseconds, and I won't do that here. I didn't archive lab runs before and after every individual change, so there is no "LCP went from 4.8s to 1.9s" table in this post — writing one would be fiction. What I can do is take each mechanism jekcms actually ships and explain, mechanically, which scoring metric it moves and why. The 97 is real; the per-lever milliseconds would not be.

Three numbers decide the score

Lighthouse weighs a handful of metrics on mobile, but in practice the score lives and dies on three: Largest Contentful Paint (how quickly the main content appears), Cumulative Layout Shift (how much the page jumps around while loading), and Total Blocking Time (how long JavaScript locks the main thread). Get those three green and the rest — FCP, Speed Index — tends to follow, because they measure overlapping things.

So every technique below is really an answer to a single question: which of the three does it move?

The first response: inline the critical CSS, defer everything else

A plain stylesheet link is render-blocking. The browser downloads the HTML, finds the link, and paints nothing until the CSS arrives. On a fast desktop connection you never notice. On the throttled mobile profile PageSpeed simulates, that one dependency chain pushes your first paint back on its own.

jekcms breaks the chain with three moves: the critical CSS for above-the-fold layout is inlined in the head, the full stylesheet loads via preload so it doesn't block rendering, and JavaScript is deferred. The pattern in the shipped themes looks like this:

<style>/* critical CSS, inlined */</style>
<link rel="preload" href="/assets/css/style.css" as="style"
      onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/assets/css/style.css"></noscript>
<script src="/assets/js/app.js" defer></script>

What this moves: LCP, because the first paint no longer waits on a network round trip for CSS. And TBT, because deferred scripts run after parsing instead of interrupting it. jekcms themes carry little JavaScript to begin with — deferring a small bundle is easy. Deferring a tag-manager pile is a different sport, and no CMS setting rescues you from it.

Images decide mobile LCP

On a content site the LCP element is almost always the featured image. jekcms runs every upload through its conversion pipeline: the image is resized to a 1920px maximum width and encoded to AVIF, with a WebP fallback generated alongside. The original never gets served — there is no code path where a visitor receives a raw multi-megabyte camera JPEG because an editor forgot to compress it. That "forgot" scenario is not hypothetical. Across multiple sites and years of uploads it is the default failure mode, and the pipeline exists precisely so nobody has to remember.

On the markup side, images go out as a picture element with both formats and explicit dimensions:

<picture>
  <source srcset="/uploads/images/sample.avif" type="image/avif">
  <source srcset="/uploads/images/sample.webp" type="image/webp">
  <img src="/uploads/images/sample.webp"
       alt="Screenshot from the post body"
       width="1200" height="800" loading="lazy">
</picture>

Two separate metrics are handled here. The AVIF and WebP sources move LCP: fewer bytes for the largest element on screen. The width and height attributes move CLS: the browser reserves the box before the file arrives, so the text below it never jumps. Below-the-fold images get loading="lazy"; the hero deliberately does not, because lazy-loading your own LCP element is a classic self-inflicted wound.

I wrote up the pipeline internals separately in how the AVIF conversion actually works.

Does server-side caching even show up in a lab score?

It does, through the metric people forget: time to first byte. The LCP clock starts at navigation, not at first paint — every millisecond your server spends thinking sits inside LCP. On shared hosting, where your PHP worker competes with the neighbors, this is the part most people prefer not to look at.

jekcms stacks three cache layers so a typical request never does real work. The page cache serves fully rendered HTML with a 300-second TTL. Below it, data and query caches catch the requests that do reach PHP. And OPcache keeps compiled bytecode in memory, so PHP isn't re-parsing source files on every hit. On a cache-hit request the CMS does approximately nothing — which is the correct amount of work for showing the same article for the ten-thousandth time. The layering has its own post if you want the internals.

One operational scar worth sharing. Our deploy script ends with an OPcache reset because of a real incident: we shipped a fix and watched the old behavior persist in production, PHP happily serving stale bytecode from before the deploy. Since then the ritual is fixed — deploy, reset OPcache, purge the page cache. The 300-second TTL doubles as a safety valve: even a missed purge heals itself within five minutes.

HTML minification, the modest one

jekcms minifies HTML output in production through an output buffer filter: whitespace between tags collapses, comments get stripped, and the contents of pre and code blocks are preserved exactly. I'll be honest about its rank — this is the smallest lever in the post. Smaller HTML helps the first round trips and costs nothing at runtime, but nobody climbs from 70 to 97 by minifying markup. It's on the list because it's real and free, not because it's heroic.

The AVIF trap that has nothing to do with your score

Convert everything to AVIF and one day you'll share a link on Facebook and get a blank card. Facebook and X don't decode AVIF for og:image. jekcms's pipeline keeps a JPG/WebP copy specifically for the social card: the page serves AVIF to browsers, while the og:image tag points at a format the crawlers actually read.

This costs you zero PageSpeed points either way. It's here because it's part of the same pipeline, and because everyone who goes all-in on AVIF hits this exact wall — usually in public.

The map: which lever moves which metric

  • LCP — the cache layers and OPcache (server answers fast), the AVIF hero (the largest element weighs less), and inline critical CSS (rendering doesn't wait for a stylesheet).
  • CLS — explicit width and height on every image, plus themes that don't inject late-loading banners or layout-shifting widgets.
  • TBT — deferred, minimal JavaScript. Less a technique than a diet.

Notice what's missing: I haven't told you how many milliseconds each lever bought, because I never measured them in isolation, and I refuse to decorate the post with plausible-sounding numbers. The mechanisms above are how these metrics work. The 97 is their sum on one real site.

What the 97 doesn't tell you

A lab score is a synthetic run on a simulated device. Real visitors on real networks see something different, and the same page will score a few points apart between two runs. Treat the number as a health check, not a trophy.

The last few points usually don't belong to the CMS at all — they belong to whatever third-party scripts you add on top. Analytics, consent banners, ad code. This is partly why jekcms ships ZeroTrack, its cookieless built-in analytics plugin: no external request, no third-party bundle competing with your content for the main thread. Put AdSense on top and you'll hand some points back. That's a business decision, not a performance failure — just make it knowingly.

For what shared hosting realistically gives you, there's a full guide on running jekcms on shared hosting; every feature referenced here is also covered in the documentation.

Written by

Celil Uyanıkoğlu

Computer engineer with 25+ years in IT. He builds jekcms and runs his own network of content sites on it — every guide published here is tried on those live installs first.

See all posts →

Order Today

One-time payment, lifetime access. Setup in 30 minutes.

View Pricing
  • Setup and live in 30 minutes
  • 13 professional themes
  • AVIF/WebP image optimization
  • Automatic SEO — Sitemap, Schema.org
  • ZeroTrack cookieless analytics

Be the first to know

New features, release notes & CMS guides — a couple of emails a month, no spam.