Guides Guide

PrestaShop Performance Optimization: Speed Up Your Store

Complete performance tuning guide — OPcache, MySQL tuning, CCC configuration, image optimization, caching strategies, and Core Web Vitals.

Speed is a conversion problem, not a vanity metric

We build Performance Revolution, our caching, Critters and PurgeCSS module, and we run it on the high-traffic shops we operate ourselves. The pattern is depressingly consistent: most PrestaShop stores are slow for the same five reasons, and four of them are fixable in an afternoon.

Speed pays out in three places:

  • Conversions. On the Hummingbird shops we run, dropping median LCP from 4s to 2s typically lifts mobile conversion by 30–60%.
  • SEO. Google has used Core Web Vitals as a ranking signal since 2021. Slow shops earn less organic traffic and pay more for paid traffic to compensate.
  • Mobile. 70% of e-commerce traffic is mobile. A page that feels fine on a MacBook can be a 6-second blank on a mid-range Android.
Speed is a maintenance discipline, not a project you finish. Every module, every theme tweak, every image nudges it up or down. We keep an LCP baseline for every client shop and check it after every deploy. See our performance tuning notes for the database, cache and measurement work behind those gains.

Measure before you touch anything

The fastest way to waste a day is to start "optimising" without numbers. Every shop we audit gets the same three-page measurement first (homepage, category, product) because each one exercises a different part of the stack.

The tools we actually use

  • Google PageSpeed Insights: Free, uses real Chrome user data (CrUX). Run it against all three page types. Category and product pages are where most stores fall apart.
  • GTmetrix: The waterfall tells you which request is blocking render, which module ships 600KB of unused CSS, and which third-party script adds 800ms before TTFB.
  • WebPageTest: Filmstrip view from different continents and connection profiles. Use when GTmetrix and PSI disagree.

Core Web Vitals — what each one tells you

  • LCP (Largest Contentful Paint): When the largest visible element finishes painting. Target under 2.5s. On most PrestaShop shops it's the hero image or category banner, and the killer is render-blocking CSS plus an unoptimised image without preload.
  • INP (Interaction to Next Paint): How fast the page reacts to a tap or click. Replaced FID in March 2024. Target under 200ms. Usual suspects: jQuery-heavy modules, tag managers, and click handlers doing too much synchronously.
  • CLS (Cumulative Layout Shift): How much the page jumps during load. Target under 0.1. Caused by images without width/height, late banners, and font swaps without a matched fallback.

Realistic targets

A real PrestaShop shop with cart logic, search, modules and tracking will not score 100. Our targets: mobile 60–80, desktop 85–95, LCP under 2.5s mobile / 1.8s desktop, total weight under 2MB, fewer than 80 requests on first paint.

Don't chase a 98 by stripping out what makes you money. A 65 with healthy conversion beats a 98 on a page nobody buys from.

Server: where most of the latency hides

No front-end cleverness will save you from a slow backend. If PrestaShop takes 800ms to render HTML before a single asset request goes out, that's your floor.

PHP OPcache — non-negotiable

OPcache holds compiled PHP bytecode in memory so the interpreter doesn't re-parse the same files on every request. PrestaShop loads hundreds of PHP files per page. Without OPcache, you pay that parse cost every time.

opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.validate_timestamps=1
opcache.revalidate_freq=60
opcache.save_comments=1

The defaults are wrong for PrestaShop. max_accelerated_files needs to be at least 20,000 — a typical PS install has 10–15k PHP files, and the default of 4,000 means most of your code gets thrown out and re-parsed. memory_consumption should be 128–256MB. If the cache fills, OPcache evicts entries and you silently lose the benefit.

On cPanel hosts with validate_timestamps=0, OPcache will never re-read PHP files from disk on its own. You must reset it via a web request after every deploy — a CLI reset only clears the CLI pool, not the web pool. We've debugged "my fix isn't deploying" tickets that turned out to be exactly this.

MySQL / MariaDB — the one setting that matters

A PrestaShop product page typically runs 80–200 SQL queries. A category page with filters can blow past 400. The single most important variable is innodb_buffer_pool_size:

[mysqld]
# Set to 50-70% of available RAM on dedicated DB server
innodb_buffer_pool_size = 1G
innodb_log_file_size = 256M
innodb_flush_log_at_trx_commit = 2

# MySQL 5.7 / MariaDB only (removed in MySQL 8.0)
query_cache_type = 1
query_cache_size = 64M

# Find problem queries
slow_query_log = 1
long_query_time = 1

# Connection settings
max_connections = 150
tmp_table_size = 64M
max_heap_table_size = 64M

If your database is 500MB and your buffer pool is 1GB, most queries serve from RAM. Turn on the slow query log and read it weekly — a query that takes 1.2s idle takes 12s under traffic, and that's how Black Friday goes sideways. Most slow queries we find come from the search controller, layered-nav filters on category pages, and stats modules that should have been disabled years ago.

Hosting tiers — be honest with yourself

  • Shared ($5–15/mo): You share CPU and RAM with hundreds of neighbours. Fine for a 200-product test shop, not a real business.
  • VPS ($20–60/mo): Where almost every shop we operate sits. 4GB RAM minimum, 2 cores, NVMe SSD. Sweet spot up to a few hundred orders a day.
  • Dedicated ($80–300/mo): 1,000+ daily orders or catalogues over 100k products. Also for anyone running their own Varnish / Redis stack.
If you're on shared hosting and slow, moving to a decent VPS buys you more speed than every other tip on this page combined. We've watched LCP drop from 6s to 2.2s on the host swap alone, nothing else changed.

PHP-FPM and Redis

Use PHP-FPM rather than mod_php — separate process pool, better memory behaviour, easier to tune for traffic spikes. Use Redis for session and cache storage instead of the filesystem. Configuration in app/config/parameters.php:

'ps_cache_enable' => true,
'ps_caching' => 'CacheRedis',

PrestaShop's built-in switches

The Performance page in PrestaShop 8 Back Office.

This is where Smarty cache, debug mode, CCC, and the caching engine live. Most performance tuning starts here.

PrestaShop 8 Back Office Performance page showing Smarty, Debug mode, CCC, and Caching settings

CCC (Combine, Compress, Cache)

Under Advanced Parameters → Performance. CCC concatenates and minifies your CSS and JS. Always enable in production. Things we've been bitten by:

  • Files marked defer or async stay separate, by design.
  • External resources (Google Fonts, Stripe.js, Cloudflare scripts) are never combined.
  • Modules that inject raw <link> tags through displayHeader instead of actionFrontControllerSetMedia bypass CCC entirely. Every audit turns up at least one. Our hooks page covers the fix.
  • If CCC breaks checkout or a widget, the module is doing something order-dependent. Find it, fix it or replace it — don't leave CCC off.

Smarty template settings

Set template caching to "Recompile templates if the files have been updated" on production. Never "Force compilation" — that recompiles every template on every request, the worst possible Smarty mode.

Debug mode — check this first, every time

Debug mode disables most caching, forces recompilation and logs aggressively. On production the only correct value is false:

// In app/config/defines.inc.php — MUST be false on production
define('_PS_MODE_DEV_', false);

We've inherited shops running in debug for months. The "mystery slowness" disappears the moment we flip the flag. Always check on the first audit pass.

Disable modules you don't use

Every enabled module costs you: autoloader entries, hook callbacks, possibly CSS/JS on every page, possibly DB queries even when there's nothing to display. A fresh install ships with a long list of modules — you don't need most of them.

Go through Modules → Module Manager and uninstall (not just disable) anything you don't actively use. Three analytics modules doing the same job? Keep one. If you need a structured cleanup pass before tuning, Cleanup Revolution helps clear operational cruft before you start measuring.

Images — usually 60–80% of your page weight

The biggest, easiest LCP wins live here. We've watched homepage LCP drop from 4.2s to 1.6s on a single shop just by regenerating images at the right sizes and serving WebP.

WebP and sensible dimensions

WebP is 25–35% smaller than JPEG with no visible quality loss. PrestaShop 8+ supports it natively; on older versions use server-side conversion or a module.

The most common waste: 2000×2000px source files served into a 300px thumbnail slot. Configure image types under Design → Image Settings to match what your theme actually renders. Don't upload 4000px masters "just in case" — 1200px is enough for retina product zoom.

Lazy loading

Defer everything below the fold:

<img src="product.jpg" loading="lazy" width="300" height="300" alt="Product Name">

Do not lazy-load the header logo, hero banner or first product row. Those are LCP candidates — lazy-loading them tells Chrome to deprioritise the element it's measuring you on. We've seen this one mistake cost a shop 1.5s of LCP. For catalogues where theme templates are inconsistent, Automatic SEO Images Lazy Tags can apply the below-the-fold pattern at scale.

Regenerating after a change

After changing image type dimensions, regenerate via Back Office or the CLI:

php bin/console prestashop:image:regenerate --type=products

Front-end optimisations that actually move the needle

Cut HTTP requests

Open your waterfall. Over 100 requests is a problem. Usual offenders: every module loading its own tiny CSS/JS file, Google Fonts with four weights and italics, social-share widgets pulling their own SDKs, two tag managers fighting each other.

Critical CSS

Browsers block rendering until every CSS file in <head> has downloaded. Critical CSS inlines the styles needed for the initial viewport into the HTML, then loads the full stylesheet asynchronously. On mobile this routinely shaves 1–2s off LCP for us. Performance Revolution generates critical CSS automatically via Critters and keeps it in sync when theme CSS changes — by hand on every theme tweak is a chore that never gets done.

JavaScript: defer is your friend

Use defer for almost everything — the script downloads in parallel but doesn't execute until HTML is parsed. Use async only for genuinely independent scripts like analytics. In PrestaShop 1.7+:

$this->context->controller->registerJavascript(
    'module-my-script',
    'modules/mymodule/views/js/front.js',
    ['position' => 'bottom', 'priority' => 200, 'attribute' => 'defer']
);

Fonts

Custom fonts quietly add 200–400KB. What we do on every shop:

  • Self-host rather than pulling from Google Fonts — saves a DNS lookup and a TLS handshake.
  • Subset to the characters you need. Latin-only is 60–80% smaller than full Unicode.
  • font-display: swap so visible text never waits on a font.
  • Two weights max. 400 and 700 cover almost every theme.
  • WOFF2 only — best compression, universal modern support.
@font-face {
    font-family: 'YourFont';
    src: url('/themes/your-theme/assets/fonts/yourfont.woff2') format('woff2');
    font-weight: 400;
    font-style: normal;
    font-display: swap;
}

CDN basics

A CDN serves static files from servers near your visitors. Cloudflare is the obvious free start. Point images, CSS and JS at the CDN domain via Advanced Parameters → Performance → Media Servers.

Database optimisation

PrestaShop databases bloat silently. Tables that were fine at launch turn into millstones after two years of cart abandonment, bot traffic and module logs nobody reads.

Clean up old carts

ps_cart and ps_cart_product get a row per visitor — including every bot that crawled the catalogue. After a year these tables routinely hit millions of rows, and every join against them slows down.

-- Delete cart products for old abandoned carts (not linked to orders)
DELETE cp FROM ps_cart_product cp
INNER JOIN ps_cart c ON cp.id_cart = c.id_cart
LEFT JOIN ps_orders o ON c.id_cart = o.id_cart
WHERE o.id_order IS NULL
AND c.date_add < DATE_SUB(NOW(), INTERVAL 3 MONTH);

-- Delete the empty carts
DELETE c FROM ps_cart c
LEFT JOIN ps_orders o ON c.id_cart = o.id_cart
LEFT JOIN ps_cart_product cp ON c.id_cart = cp.id_cart
WHERE o.id_order IS NULL AND cp.id_cart IS NULL
AND c.date_add < DATE_SUB(NOW(), INTERVAL 3 MONTH);
Back up first, staging first. The LEFT JOIN + IS NULL pattern is what protects carts that became orders — get it wrong and you orphan order history.

Logs and connections

-- Application logs
DELETE FROM ps_log WHERE date_add < DATE_SUB(NOW(), INTERVAL 30 DAY);

-- Visitor tracking
DELETE FROM ps_connections_page WHERE id_connections IN (
    SELECT id_connections FROM ps_connections
    WHERE date_add < DATE_SUB(NOW(), INTERVAL 3 MONTH)
);
DELETE FROM ps_connections WHERE date_add < DATE_SUB(NOW(), INTERVAL 3 MONTH);

-- Orphaned guest records
DELETE g FROM ps_guest g
LEFT JOIN ps_connections c ON g.id_guest = c.id_guest
WHERE c.id_guest IS NULL;

-- Search stats, 404 logs, email logs
DELETE FROM ps_statssearch WHERE date_add < DATE_SUB(NOW(), INTERVAL 6 MONTH);
DELETE FROM ps_pagenotfound WHERE date_add < DATE_SUB(NOW(), INTERVAL 30 DAY);
DELETE FROM ps_mail WHERE date_add < DATE_SUB(NOW(), INTERVAL 3 MONTH);

Search index

If you use Elasticsearch, Algolia or any external search backend, the built-in search tables are dead weight PrestaShop still rebuilds:

TRUNCATE TABLE ps_search_index;
TRUNCATE TABLE ps_search_word;

Optimise tables and add indexes

After big deletions, reclaim space:

OPTIMIZE TABLE ps_cart, ps_cart_product, ps_connections,
    ps_connections_page, ps_guest, ps_log;

Indexes we've added on nearly every shop we audit:

-- Check existing indexes first: SHOW INDEX FROM ps_cart;
ALTER TABLE ps_cart ADD INDEX idx_cart_date (date_add);
ALTER TABLE ps_connections ADD INDEX idx_conn_date (date_add);
ALTER TABLE ps_orders ADD INDEX idx_orders_ref (reference);
ALTER TABLE ps_product ADD INDEX idx_product_ref (reference);

Run EXPLAIN on queries from your slow log. If type says ALL, the database is full-scanning. The right index typically turns a 4-second query into 12ms.

Caching strategies

Full page cache

The biggest single lever you have. Without it, every anonymous request executes the whole stack — hundreds of PHP files, 100+ queries, 200–500ms of render. With it, the same page goes out in 5–20ms. On the Hummingbird shops we run, enabling FPC typically cuts mobile LCP by 1.2–1.8s on top of everything else.

Varnish is the industry default. nginx FastCGI cache is simpler if you already run nginx:

fastcgi_cache_path /var/cache/nginx levels=1:2 keys_zone=PS:100m inactive=60m;

set $skip_cache 0;
if ($http_cookie ~* "PrestaShop-") { set $skip_cache 1; }
if ($request_uri ~* "/cart|/order|/my-account|/admin") { set $skip_cache 1; }

location ~ \\.php$ {
    fastcgi_cache PS;
    fastcgi_cache_valid 200 10m;
    fastcgi_cache_bypass $skip_cache;
    fastcgi_no_cache $skip_cache;
}

The hard part isn't enabling it — it's invalidation. When a price changes, stock drops, or an admin edits a category, the right cached pages have to drop. That complexity is why so many shops never turn FPC on. It's also what Performance Revolution handles for you on PrestaShop.

Object caching and browser caching

Object caching (Redis) keeps query results in memory between requests. Smaller gain than FPC (typically 30–50% off query time) but invalidation is automatic, so it's the safe first step.

Browser caching headers tell visitors' browsers to keep static files locally:

# Apache (.htaccess)
<IfModule mod_expires.c>
    ExpiresActive On
    ExpiresByType image/jpeg "access plus 1 year"
    ExpiresByType image/webp "access plus 1 year"
    ExpiresByType text/css "access plus 1 month"
    ExpiresByType application/javascript "access plus 1 month"
    ExpiresByType font/woff2 "access plus 1 year"
</IfModule>

The things that quietly destroy performance

  • Too many enabled modules. Every one costs you — autoloader entries, hook callbacks, often CSS/JS, often queries. A lean shop almost always beats a bloated one on identical hardware. No module is free.
  • Modules loading assets on every page. A popup module that fires only on the homepage but ships 150KB of JS on every product page is pure waste. Scan your waterfall for module asset paths where they shouldn't be.
  • Hero sliders. 3–5 high-res images plus a JS carousel library, often 1–5MB total, for a component fewer than 1% of users interact with past slide one. We replace these with a single static hero on almost every redesign.
  • Themes shipped without performance review. Full Bootstrap for three components, unminified bundles, no image dimensions, synchronous scripts. Ask theme developers for a Lighthouse score on the demo before you buy.
  • Missing indexes. A query with the right index runs in 10ms; without one, the same query at peak traffic runs for 5s and queues every request behind it. You won't notice until Black Friday.

Performance monitoring checklist

Quick checks (5 minutes)

  • PageSpeed Insights on homepage, category, product
  • _PS_MODE_DEV_ is false
  • Smarty not on "Force compilation"
  • CCC enabled
  • OPcache active per phpinfo()

Monthly maintenance (30 minutes)

  • Clean abandoned carts older than 3 months
  • Clean ps_log, ps_connections, ps_connections_page, ps_guest
  • Run OPTIMIZE TABLE on the cleaned tables
  • Review the slow query log
  • Uninstall any unused modules

Quarterly review (2 hours)

  • Full GTmetrix test from multiple locations, compared to last quarter
  • Review Core Web Vitals in Google Search Console
  • Audit module assets for unnecessary CSS/JS
  • Check image sizes for oversized uploads
  • Review server resource usage (CPU, RAM, disk I/O)
  • Test on a real mid-range mobile device, not Chrome's emulator
  • Verify browser caching headers are still in place
  • Check PHP error logs (exceptions burn resources even when nobody sees them)

After every deployment

  • Clear PrestaShop cache
  • Reset OPcache web pool if validate_timestamps=0
  • Quick PageSpeed test for regressions
  • Check the browser console for JS errors
  • Walk the checkout end-to-end

Where to start

Four things, in order: fix server config (OPcache + InnoDB buffer pool + PHP-FPM), regenerate and serve images at the right sizes in WebP, uninstall modules you don't use, clean up the database. That covers the bulk of the performance problems we find on audits.

Measure before and after every change. Keep a short changelog of what you did and what the numbers did. The optimisation that matters is the one your customers feel: faster product pages, a responsive checkout. That translates into trust, lower bounce and higher conversion.

Related reading

Loading...
Back to top