Skip to main content
Joomla Template Performance Optimization for Developers
On this page
# Topics

Joomla Template Performance Optimization for Developers

05 August 2026

Two Joomla sites can run on the same server, with the same PHP version, the same caching, and the same extensions - and one still feels twice as fast. The difference is usually the template. The server decides how quickly the HTML leaves the building; the template decides how much work the browser must do after that: how much CSS to parse, how much JavaScript to run, which fonts to fetch, and how many DOM nodes to lay out. That browser-side work is where most of the perceived speed lives.

This article is the developer-tier companion to the general Joomla performance article: it focuses on the template layer. It explains how Joomla assembles a page, how the Web Asset Manager decides what loads, and how to optimize markup, CSS, JavaScript, fonts, and images inside your template and overrides. Site builders will get practical recipes; developers will see exactly where in the Joomla code each mechanism lives.

The server sends the page once. The browser "pays" (performance costs) for your template on every single visit.

The goal is simple: help you build or tune a Joomla template that renders fast on real devices, not just on your development machine.

1. The Basics

1.1 Where Template Performance Sits

Loading a page has two halves. First the server half: Joomla runs PHP, queries the database, and produces HTML. That determines the Time To First Byte (TTFB). Then the browser half: the browser parses the HTML, downloads the CSS, JavaScript, fonts, and images the template references, builds the layout, and paints the screen. That determines First Contentful Paint, Largest Contentful Paint, and how "snappy" the page feels.

The template dominates the second half almost completely. A template decides:

  • How many CSS and JavaScript files load, and how large they are.
  • Whether fonts come from your server, from Google, or from the visitor's own system.
  • How many DOM nodes the browser must build and lay out.
  • Whether images in the layout (logo, banners) load eagerly or lazily.
  • Whether assets load on every page or only where they are used.

1.2 The Budget Mindset

Think of every page as having a budget: kilobytes to download, milliseconds to render. Every stylesheet, script, font, and wrapper <div> spends from that budget. A fast template is not a template with clever tricks; it is a template that spends little. Cassiopeia, Joomla's default template, is a good study object: its own JavaScript is under 1 KB minified, and it makes fonts and colors optional extras instead of defaults.

1.3 Measure First

Before changing anything, measure. Use Lighthouse (in Chrome DevTools) or PageSpeed Insights for the browser half, and Joomla's Debug System plugin for the server half. If TTFB is slow, the template is not your problem - start with the general performance article. If TTFB is fine but the page still paints late, keep reading: the template layer is where you will win.

Back to top

2. Under the Hood: How Joomla Renders a Page

To optimize the template layer, it helps to know the exact order in which Joomla builds a page. The pipeline is short but not obvious.

2.1 The Pipeline

Request
  ↓
SiteApplication::dispatch()
  ↓  the COMPONENT runs first; its HTML is stored in a buffer
  ↓  ($document->setBuffer($contents, ['type' => 'component']))
  ↓
your template's index.php executes
  ↓  registers assets, builds the page skeleton,
  ↓  leaves <jdoc:include /> placeholders in the markup
  ↓
HtmlDocument::parse() collects all <jdoc:include> tags
  ↓
HtmlDocument render: every tag is replaced by its renderer
     component → the buffer stored earlier
     modules   → rendered NOW, position by position
     head      → assembled LAST from everything registered so far
  ↓
HTML response

Three practical conclusions follow from this order:

  • The component runs before your template file. By the time index.php executes, the main content already exists. Your template cannot make the component faster - but it decides everything around it.
  • Modules render at replacement time. Each <jdoc:include type="modules" name="..." /> triggers real module code when the placeholder is replaced. Every filled position costs PHP time and adds markup.
  • The head is assembled last. Even though <jdoc:include type="head" /> sits at the top of your file, Joomla fills it at the end of rendering. That is why a component, module, or override can still register a stylesheet "too late" in your mental model - and it works anyway.

2.2 What the Browser Does Next

The HTML response is the end of Joomla's work, but only the middle of the page load. The browser runs its own pipeline, and every template decision lands somewhere on it:

HTML → DOM           parse the markup into a node tree
CSS  → CSSOM         parse all stylesheets (rendering waits for this)
DOM + CSSOM → render tree → layout → paint → composite

This staircase explains the vocabulary of the rest of this article. "CSS blocks rendering" means the browser cannot paint before the CSSOM is complete - which is why stylesheet size and count matter. Synchronous JavaScript pauses the HTML parsing on the first step. A big DOM makes the layout step expensive. Every optimization in the following sections shortens one specific step.

2.3 Where This Lives in the Code

The whole mechanism sits in libraries/src/Document/HtmlDocument.php (the parse() and _renderTemplate() methods, which regex-match the <jdoc:include> tags) and the renderer classes in libraries/src/Document/Renderer/Html/: ComponentRenderer, ModulesRenderer, ModuleRenderer, HeadRenderer, MetasRenderer, StylesRenderer, ScriptsRenderer, and MessageRenderer. Reading these files once demystifies the entire template layer.

Back to top

3. The Web Asset Manager: Load Only What You Need

Since Joomla 4, assets are not loose <link> and <script> tags but named items in a dependency graph, managed by the Web Asset Manager. This is the single most important performance tool in the template layer.

3.1 The Asset Registry: joomla.asset.json

Every template and extension can ship a joomla.asset.json file that declares its assets: a name, a file, dependencies, and options. Cassiopeia's registry (in templates/cassiopeia/joomla.asset.json) declares, among others:

  • template.cassiopeia.ltr - the main stylesheet, with a dependency on fontawesome.
  • template.cassiopeia - the template script, loaded with "type": "module" so it never blocks parsing.
  • template.user - the optional user.css / user.js, with "weight": 500 so it always loads last and wins the cascade.
  • template.active - an empty "dummy" asset that extensions can depend on to load after the active template.

Because dependencies are declared, Joomla loads each asset once, in the correct order, no matter how many extensions ask for it. You never manage script order by hand again.

3.2 Presets: One Switch for a Bundle

A preset groups several assets under one name. Cassiopeia's index.php enables its whole frontend with one call:

$wa = $this->getWebAssetManager();
$wa->usePreset('template.cassiopeia.' . ($this->direction === 'rtl' ? 'rtl' : 'ltr'));

The preset pulls in the stylesheet and the script, and their dependencies come along automatically. Note the detail: the RTL stylesheet only loads for right-to-left languages. That is conditional loading at the template level - the LTR visitor never pays for RTL rules.

3.3 Conditional Loading in Overrides

The biggest win for real sites: load an asset only on pages that use it. A template override is the perfect place, because it only runs when that view renders:

<?php
// In an override, e.g. templates/cassiopeia_child/html/com_content/article/default.php
$wa = $this->getDocument()->getWebAssetManager();

$wa->registerAndUseScript(
    'mysite.gallery',                       // asset name
    'templates/cassiopeia_child/gallery.js', // file
    ['version' => 'auto'],                  // options
    ['defer' => true]                       // attributes
);

The gallery script now loads on article pages only. Your homepage, contact page, and category lists never see it. This one pattern - move page-specific assets from index.php into overrides - is often worth more than any minification plugin.

3.4 Cache Busting with version: auto

The ['version' => 'auto'] option appends Joomla's media version to the URL (for example template.min.css?a1b2c3). The hash is generated in libraries/src/Version.php from the Joomla version, your secret, and a date, and it changes on every update and cache clear. This lets you send long browser-cache lifetimes for static assets without ever serving a stale file after an update.

Back to top

4. Lean Markup: DOM Weight and Module Positions

4.1 The DOM is Not Free

Every element in the page costs memory and layout time. Lighthouse starts warning around 1,500 DOM nodes; heavily nested page-builder output can triple that. The browser must style, lay out, and paint each node - and JavaScript that touches the DOM slows down as the tree grows. Semantic, flat markup is a performance feature, not just a code style preference. It also helps accessibility: when I build child templates for client sites, I check WCAG compliance and performance together, because lean semantic markup improves both at once.

4.2 Skip Empty Positions with countModules()

A good template never renders wrappers for empty module positions. Cassiopeia checks first:

<?php if ($this->countModules('sidebar-left', true)) : ?>
<div class="grid-child container-sidebar-left">
    <jdoc:include type="modules" name="sidebar-left" style="card" />
</div>
<?php endif; ?>

The second argument true makes the count respect menu assignments and access levels, so a module assigned to other pages does not leave an empty sidebar column behind. Without this check you get phantom columns, stray margins, and CSS workarounds that cost more markup again.

4.3 Chrome: The Wrapper Around Every Module

The style="card" attribute selects the module chrome - the wrapper markup rendered around each module. Joomla resolves it through LayoutHelper::render('chromes.' . $style) in ModuleHelper, and Cassiopeia ships two chromes in html/layouts/chromes/: card.php (a Bootstrap card with heading) and noCard.php (minimal). Choosing style="none" renders the module output with no wrapper at all. Multiply the chrome cost by the number of modules on the page: ten modules with a heavy chrome add dozens of extra nodes for pure decoration.

Back to top

5. CSS Strategy

5.1 Know What You Are Shipping

Numbers from a default Joomla 6 installation: Cassiopeia's template.min.css is about 247 KB uncompressed (it contains all of Bootstrap), and its fontawesome dependency adds about 100 KB of icon CSS. Compression shrinks this a lot on the wire - Joomla even ships precompressed .gz files next to every minified asset - but the browser still parses the full amount. A custom template that includes only the CSS it uses can be a tenth of that size.

5.2 One File Beats Many - Usually

With HTTP/2, the old "combine everything into one file" rule softened, but a pile of small stylesheets still costs discovery time, and @import chains are the worst case: each import is only discovered after the previous file downloads. Keep it simple: one compiled, minified template stylesheet, plus user.css for site-specific tweaks. If your user.css grows past a few hundred lines, it is time to move the rules into a child template's compiled stylesheet.

5.3 Inline the Critical, Defer the Rest

Cassiopeia inlines its CSS custom properties (colors, fonts) with $wa->addInlineStyle() directly in index.php - a tiny critical block that needs no extra request. The same idea scales up: inline the small amount of CSS needed to paint the top of the page, and load the rest without blocking. Joomla has a built-in mechanism for the non-blocking part, which you will see in the font section: the rel="lazy-stylesheet" attribute.

5.4 Modern CSS: Let the Browser Skip Work

Two newer CSS features give templates a direct rendering win, with no JavaScript:

/* Do not render off-screen sections until the visitor scrolls near them */
.site-footer,
.below-fold-section {
    content-visibility: auto;
    contain-intrinsic-size: auto 400px; /* reserve space, avoid layout shift */
}

/* Tell the browser a widget's internals cannot affect the outside layout */
.module-card {
    contain: layout style;
}

content-visibility: auto lets the browser skip layout and paint for off-screen content - on long pages this measurably improves initial rendering. contain limits how far layout recalculations ripple. Both are safe to add in user.css or a child template and degrade gracefully in older browsers.

Back to top

6. JavaScript Strategy

6.1 Never Block the Parser

A plain <script src> in the head stops HTML parsing until the script downloads and runs. Joomla's own assets avoid this: core scripts load with defer or as ES modules (type="module", which defers by nature - Cassiopeia's template.js is declared exactly that way in its asset registry). Follow the same rule for your own scripts: pass ['defer' => true] or ['type' => 'module'] as attributes when registering them with the Web Asset Manager.

6.2 Bootstrap JavaScript is Opt-in, Per Component

Joomla does not load Bootstrap's JavaScript globally. Each interactive component is a separate small file in media/vendor/bootstrap/js/, activated through a helper:

<?php
use Joomla\CMS\HTML\HTMLHelper;

// Loads ONLY the collapse script, not the whole Bootstrap bundle
HTMLHelper::_('bootstrap.collapse', '.selector');

Call the helper in the override that needs it - an accordion on one page should not ship carousel, modal, and dropdown code to every page. If your template framework loads bootstrap.bundle.min.js wholesale, that is a red flag.

6.3 The Template's Own Script Should Be Small

Cassiopeia's entire template JavaScript is 979 bytes minified (575 bytes over the wire with gzip). It handles the back-to-top button and little else. That is the right ambition for a template: behavior belongs to the components and modules that need it, loaded conditionally; the template script only glues the layout together. For the wider JavaScript picture (the import map, core assets, jQuery's optional status), see the dedicated JavaScript article.

Back to top

7. Font Strategy: The Cassiopeia Lesson

Fonts are often the single largest render-blocking cost in a template - and Cassiopeia's font handling is a masterclass you can copy. It offers four choices, in increasing order of cost:

7.1 Option Zero: No Font Scheme

The default. The template uses its built-in stack and downloads nothing. Zero requests, zero bytes, zero layout shift.

7.2 System Font Stacks: Free and Instant

The useFontScheme option in the template style offers curated system font stacks (Transitional, Humanist, Geometric, Monospace, and more). These map to fonts already installed on the visitor's device - for example Inter, Roboto, 'Helvetica Neue', Arial, sans-serif. The text renders immediately in the first paint. For most business sites, a well-chosen system stack is visually indistinguishable from a webfont and costs nothing.

7.3 Local Fonts: Self-Hosted, Preloaded

The "Roboto (local)" option loads a stylesheet from your own server (media/templates/site/cassiopeia/css/global/fonts-local_roboto.css). Look at how index.php loads it:

$wa->registerAndUseStyle('fontscheme.current', $paramsFontScheme,
    ['version' => 'auto'], ['rel' => 'lazy-stylesheet']);
$this->getPreloadManager()->preload(
    $wa->getAsset('style', 'fontscheme.current')->getUri() . '?' . $this->getMediaVersion(),
    ['as' => 'style']
);

Two tricks at once: the stylesheet is preloaded (the browser starts fetching it immediately) but linked as rel="lazy-stylesheet", so it does not block rendering. Joomla's StylesRenderer also emits a <noscript> fallback link for visitors without JavaScript. Self-hosting also keeps you on the safe side of the GDPR - no visitor IP addresses go to a third party just to fetch a font.

7.4 Google Fonts: The Full Mitigation Kit

If you do pick the web font option, Cassiopeia shows the damage control: it calls preconnect for both fonts.googleapis.com and fonts.gstatic.com (so DNS, TCP, and TLS happen early), preloads the stylesheet, and again loads it lazily with the noscript fallback. Copy this pattern for any third-party resource you cannot avoid. And in your own @font-face rules: use WOFF2 only, add font-display: swap, and subset the character set if the font file allows it. Consider a variable font while you are at it: one variable WOFF2 file covers every weight from light to bold, replacing three or four separate downloads.

Back to top

8. Images in the Template Layer

8.1 The Logo Loads Eagerly - On Purpose

Cassiopeia renders its logo with ['loading' => 'eager', 'decoding' => 'async']. That is deliberate: the logo sits above the fold and is often part of the Largest Contentful Paint. Never lazy-load the LCP element. Lazy loading a header image is one of the most common self-inflicted performance wounds - it delays exactly the paint that Core Web Vitals measure.

You can go one step further than eager loading: add fetchpriority="high" to the one image that is your LCP element. Where loading="eager" means "do not delay this", fetchpriority="high" tells the browser to fetch it before other resources compete for bandwidth. Cassiopeia does not set it by default, but HTMLHelper::_('image') passes any attribute through, so an override can.

8.2 Lazy-Load Below the Fold, in Overrides

Joomla's core article layouts do not add loading="lazy" to intro and full-article images, so the template layer is where you add it. In a blog override, mark images from the second card onward:

<img src="/..." alt="..."
     loading="lazy" decoding="async"
     width="800" height="450">

Always keep width and height (or an aspect-ratio rule in CSS): the browser then reserves the space and the layout does not jump when the image arrives. Layout shift is a template problem more than an image problem.

8.3 Background Images and CSS

Hero sections built with CSS background-image cannot use native lazy loading or srcset. Where the image is content, prefer a real <img> in the markup - it is faster, responsive, and accessible. Reserve CSS backgrounds for decoration. Image formats, compression, and the media manager's resizing tools are covered in the general performance article.

Back to top

9. Resource Hints and the Preload Manager

Joomla wraps browser resource hints in a small API on the document: the PreloadManager (libraries/src/Document/PreloadManager.php). You have already seen it in the font section; here is the full toolbox:

<?php
$doc = Joomla\CMS\Factory::getApplication()->getDocument();
$pm  = $doc->getPreloadManager();

$pm->preconnect('https://cdn.example.com/');       // open the connection early
$pm->dnsPrefetch('https://stats.example.com/');    // resolve DNS early (cheaper)
$pm->preload('/media/.../hero.woff2',              // fetch a critical file now
    ['as' => 'font', 'crossorigin' => 'anonymous']);
$pm->prefetch('/likely-next-page');                // idle-time fetch for later

A detail most developers miss: Joomla does not print these as <link> tags in the head. The preloadAssets() method in libraries/src/Document/Document.php serializes them into an HTTP Link response header. The browser sees the hints in the headers before it parses a single byte of HTML - earlier than any head tag could act. It also means well-configured servers can turn them into HTTP 103 Early Hints.

Use hints sparingly. Preloading everything preloads nothing: each hint competes for the same bandwidth as the page itself. Two or three hints for genuinely critical resources (the main font, one third-party origin) is the sweet spot.

Back to top

10. Caching at the Template Level

10.1 Module Caching Modes

Since modules render during template output (section 2), module caching is template-layer caching. A module that declares caching support can use one of four modes, resolved in ModuleHelper::moduleCache():

ModeCache keyTypical use
static One cache entry for all pages with the same module parameters Footer text, banners
itemid One entry per menu item Menus, breadcrumbs
safeuri Entry per whitelisted URL parameters Filtered lists
id The module computes its own key Complex custom modules

Module caching switches off automatically for logged-in users. If you develop modules: declaring the right cache mode in your module is a bigger gift to site owners than any code micro-optimization.

10.2 What Page Caching Does to Your Template

With full page caching (the System - Page Cache plugin), your template's PHP does not run at all for cached hits - the stored HTML is served directly. That makes the template's output the thing being cached: a lean 60 KB page caches and serves faster than a 400 KB one, even from cache. Caching layers and their pitfalls have their own article; the point here is that page caching does not excuse a heavy template - it just moves the cost around.

Back to top

11. Measuring the Template

11.1 Separate the Two Halves

In your measuring tool, split the metrics: TTFB belongs to the server half; everything after it (First Contentful Paint, Largest Contentful Paint, total blocking time, layout shift) belongs mostly to the template half. A page with a 200 ms TTFB and a 4-second LCP has a template problem, and no amount of server tuning will fix it.

Also separate lab data from field data. Lighthouse runs a one-off test on one simulated device (lab); the Core Web Vitals report in Google Search Console and the top block of PageSpeed Insights show what real visitors measured over the past 28 days (field). A good lab score combined with failing field data usually means your real audience uses slower devices or networks than your test does. Trust the field numbers - they are what counts for ranking.

11.2 Three Browser Tools That Pay Rent

  • Lighthouse - the overall score, plus specific flags: render-blocking resources, unused CSS/JS, DOM size, missing image dimensions.
  • Coverage panel (DevTools) - shows the percentage of each CSS/JS file actually used on the current page. Brutally honest about framework bloat.
  • Network throttling - test on "Slow 4G" with CPU throttling. Your visitors' phones are slower than your development machine; a template is only fast if it is fast there.

11.3 Joomla's Own View

Enable the Debug System plugin and read the profiler bar: afterRenderModule entries show what each module position costs in milliseconds, and the asset list shows every file the Web Asset Manager decided to load, with its dependency chain. It is the fastest way to spot the one module or asset that does not belong on this page.

Back to top

12. Template Performance and the Web Services API

The API application does not use your template at all. A request to /api/index.php/v1/content/articles renders through the JsonapiDocument class - no index.php, no Web Asset Manager, no modules, no chrome. This has two practical consequences. First: template optimization does nothing for API consumers, so headless setups shift the entire browser-half budget to the frontend framework you build there. Second, the reverse: an API-heavy site still needs a fast template for the pages humans visit - the two performance budgets are independent.

curl -H "X-Joomla-Token: <token>" \
  https://example.test/api/index.php/v1/content/articles
# Pure JSON: not a single template asset involved
Back to top

13. SEO and Metadata

Core Web Vitals are a ranking signal, and the template controls all three: the Largest Contentful Paint (fonts, render-blocking CSS, the hero image), Cumulative Layout Shift (image dimensions, font swapping, late-loading banners), and interaction responsiveness (JavaScript weight). A template rebuild is therefore an SEO project as much as a design project.

Two template-specific SEO details: keep the <meta name="viewport"> tag intact (Cassiopeia sets width=device-width, initial-scale=1; without it, mobile-friendliness fails outright), and make sure content hidden by accordions or tabs is present in the HTML - search engines index it fine, but content injected only by JavaScript after interaction is riskier. Lean, semantic markup with a single <h1> and a logical heading tree helps crawlers and screen readers alike.

Back to top

14. Common Mistakes and Pitfalls

14.1 Everything Loads Everywhere

Symptom: The gallery script, the map library, and the slider CSS load on every page, including the contact form.

Fix: Move page-specific assets out of index.php into the overrides of the views that use them (section 3.3). The Web Asset Manager keeps the order correct wherever you register them.

14.2 The @import Chain

Symptom: user.css starts with three @import lines; stylesheets load one after another instead of in parallel, and the page paints late.

Fix: Register each stylesheet with the Web Asset Manager (or combine them into one compiled file in a child template). @import serializes downloads; the asset graph parallelizes them.

14.3 Lazy-Loading the Hero

Symptom: Lighthouse reports a poor LCP even though images are "optimized"; the header image visibly pops in.

Fix: Remove loading="lazy" from anything above the fold. Follow Cassiopeia's example: the logo loads with loading="eager". Lazy-load only what starts off-screen.

14.4 An Icon Font for Three Icons

Symptom: The template pulls in the full icon-font CSS (about 100 KB in the default setup) to show a search glyph, a phone, and an arrow.

Fix: Use inline SVG for a handful of icons. If your child template does not use the icon classes at all, register a template stylesheet without the fontawesome dependency instead of inheriting it.

14.5 Testing Only on a Fast Machine

Symptom: "It is fast on my machine", but real visitors on mid-range phones wait seconds.

Fix: Always test with CPU and network throttling enabled, and check the field data (Core Web Vitals) in Google Search Console - it reflects real devices, not your workstation.

14.6 The Blind Optimizer Plugin

Symptom: A minify-and-combine extension is switched on; some pages break, ES module scripts stop working, and the "optimized" bundle changes on every page so nothing caches.

Fix: Joomla already ships minified assets, precompressed .gz variants, a dependency-ordered loader, and cache-busting versions. Fix the template layer first; add an optimizer plugin only for a measured, remaining problem - and re-measure after enabling it.

Back to top

15. Best Practices

If you remember only a few things from this article, remember these:

  • Measure first, and split the halves: TTFB is the server, everything after is mostly the template.
  • Register every asset through the Web Asset Manager, with defer or type="module" for scripts and ['version' => 'auto'] for cache busting.
  • Load page-specific assets in overrides, not in index.php.
  • Wrap module positions in countModules() checks and pick the lightest chrome that works.
  • Prefer system font stacks; if you need webfonts, self-host them and copy Cassiopeia's preload-plus-lazy-stylesheet pattern.
  • Never lazy-load the LCP element; always set image dimensions.
  • Use content-visibility: auto on long below-the-fold sections - it is one CSS rule for a real rendering win.
  • Keep the template's own JavaScript tiny; Cassiopeia manages with less than 1 KB.
  • Test throttled, and read the Coverage panel before adding any new library.
Back to top

16. Quick Reference

RENDER PIPELINE
  component runs first → template index.php → jdoc tags parsed
  → modules render per position → head assembled last

WEB ASSET MANAGER (in index.php or an override)
  $wa = $this->getWebAssetManager();          // template
  $wa = $this->getDocument()->getWebAssetManager(); // override
  $wa->usePreset('template.cassiopeia.ltr');
  $wa->useScript('bootstrap.collapse');       // one component, not the bundle
  $wa->registerAndUseScript('my.js', 'path/file.js',
      ['version' => 'auto'], ['defer' => true]);
  $wa->addInlineStyle(':root { --brand: #001b4c; }');

RESOURCE HINTS (sent as HTTP Link header)
  $doc->getPreloadManager()->preconnect('https://origin/');
  $doc->getPreloadManager()->preload('/path/font.woff2',
      ['as' => 'font', 'crossorigin' => 'anonymous']);

FONT DECISION LADDER (cheapest first)
  none → system stack → self-hosted (preload + lazy-stylesheet)
       → Google Fonts (preconnect x2 + preload + lazy-stylesheet)

TEMPLATE CHECKLIST
  [ ] countModules() around every optional position
  [ ] lightest chrome (card / noCard / none) per position
  [ ] page-specific assets moved into overrides
  [ ] loading="eager" + fetchpriority="high" on the LCP image, lazy below
  [ ] width + height on every layout image
  [ ] content-visibility: auto on long footers/sections
  [ ] tested with CPU + network throttling

KEY FILES
  templates/<tpl>/joomla.asset.json      asset registry
  templates/<tpl>/index.php              pipeline + asset activation
  templates/<tpl>/html/layouts/chromes/  module wrappers
  libraries/src/Document/HtmlDocument.php     jdoc parsing
  libraries/src/Document/Renderer/Html/       the renderers
  libraries/src/Document/PreloadManager.php   resource hints
Back to top

17. Summary

  • The server determines TTFB; the template determines nearly everything the visitor experiences after it.
  • Joomla renders the component first, runs your template file, then replaces <jdoc:include> tags - modules render at that moment, the head last.
  • The Web Asset Manager is the central performance tool: named assets, dependency ordering, presets, conditional loading in overrides, and automatic cache busting.
  • Lean markup pays twice: fewer DOM nodes render faster and are easier to make accessible. countModules() and light chromes keep the tree small.
  • Fonts follow a cost ladder from "none" to Google Fonts; Cassiopeia demonstrates the mitigation pattern (preconnect, preload, lazy stylesheet) when you climb it.
  • Resource hints travel as an HTTP Link header, ahead of the HTML itself - powerful, and best used sparingly.
  • Module cache modes, image loading attributes, and modern CSS like content-visibility finish the toolkit.

A fast template is mostly the sum of small, deliberate decisions: one less stylesheet, one deferred script, one skipped module wrapper. If your scores stay low after tuning and you suspect the template or its extensions are the cause, it pays to have someone measure the rendering pipeline systematically - the bottleneck is usually in the last place the page builder looked.

Back to top
Joomla Template Performance Optimization for Developers
Peter Martin
Peter Martin
Joomla Specialist

Peter is a Joomla specialist and a Linux admin for fast, secure and scalable websites.