Performance and caching
Full-page cache, fragment caches, OPcache, optimize, Redis, queues and server headers. What is cached, when it refreshes, and what to do when a change does not show.
Sellora is built to answer most storefront requests from a ready-made copy of the page. On ordinary shared hosting with the file cache, a cached page is answered in a few milliseconds, without starting the application or querying the database. Open Settings > Performance to see the status and to change the options.
What is cached#
| Layer | What | Who gets it | Refreshed |
|---|---|---|---|
| Full-page cache | Complete HTML of the home page, shop, categories, brands, tags, product pages, search results, blog, CMS pages, About, FAQ, Contact, the quick view and the product reviews pages | Visitors who are not signed in, have an empty cart, wishlist and compare list and see no message (for example "Saved" or form errors) | On every change in the admin, and after the page lifetime (default 10 minutes) |
| Early hits | The same cached pages, answered from public/index.php before Laravel starts |
First-time visitors and crawlers without a language or currency cookie (file cache only) | Same as above |
| Fragment caches | Home sections (rendered HTML), menus, category tree, sliders, flash sale, currencies, languages, settings, related products and "more from this brand" (product id lists), review statistics | Everybody, including signed-in customers | On every change in the admin (shared version key) and after one hour |
| Browser cache | Cached pages are sent with ETag and Last-Modified; browsers check back each time and get a tiny "304 Not Modified" when nothing changed |
Everybody | Every visit (checked) |
Never cached: the cart, checkout, account pages, wishlist and compare pages, sign-in and sign-up pages, order tracking, the admin, the installer, the REST API and payment pages. These pages are sent with Cache-Control: private, no-store.
Every storefront response carries the header X-Sellora-Cache: HIT, MISS (built now and stored) or BYPASS (built for this visitor only). Early hits also send X-Sellora-Cache-Layer: early.
Personal parts of a cached page#
A cached page is the same for every visitor who receives it, so it contains nothing personal:
- The CSRF security token is left empty in the stored copy. The storefront script loads a fresh token from
GET /stateas soon as the page opens, and any form sent before that waits for it. Add to cart, newsletter, reviews and every other form work as usual. GET /statealso records what the full page would have recorded: the visit for Reports > Analytics, the product view and the "Recently viewed" list, blog post views and the search term for Reports > Search terms.- As soon as a visitor signs in, adds something to the cart, wishlist or compare list, or sees a message, the browser receives a small
sl_nccookie and gets freshly built pages from then on. Visitors with a "Recently viewed" list (sl_rvcookie) get freshly built product pages.
Language, currency and demo#
The cache key contains the address, the query string (tracking parameters such as utm_source, gclid and fbclid are ignored), the language, the currency and, in the live demo, the demo store. A visitor who switches to Euro or Arabic gets the Euro or Arabic copy; the first visitor in each combination builds it.
When the cache refreshes#
You do not need to clear anything after normal work:
- Saving anything in the admin (products, categories, brands, menus, sections, sliders, banners, pages, posts, reviews, flash sales, coupons, settings, appearance, translations) refreshes every cached page and fragment.
- Changes made outside the admin through the application (imports, the REST API, the demo installer,
php artisancommands) refresh the cache too, because Sellora watches the catalogue and content records. - Stock and sales counters do not refresh the cache on every order, so a busy store keeps its cache. Product pages show the new stock after at most the page lifetime; the cart and checkout always check the real stock.
- Time-based changes (a flash sale starting or ending, special price dates, "new" badges) appear after at most the page lifetime. Lower Keep pages for (minutes) if you need them sooner.
Settings > Performance#
| Field | Notes |
|---|---|
| Enable the full-page cache | On by default. Turn it off while you edit theme files. |
| Keep pages for (minutes) | Default 10. Also the longest delay for time-based changes. |
| Serve cached pages before the application starts | Early hits (file cache only). Fastest option; turn off only when a plugin must run on every request. |
| Browser cache (seconds) | Default 0: browsers revalidate every time with ETag (recommended). |
| Warm the cache every 30 minutes | Requests the main pages from the cron job so visitors never wait for a page to be built. |
| Never cache these paths | Paths (with * wildcards) that must always be built fresh, e.g. page/live-stock. |
| Cache menus, footers, listings ... | Fragment caches. Leave on. |
Below the form:
- Cache status: driver, number and size of cached pages, OPcache, optimized files, queue, Redis, last warm-up.
- Warm cache: requests the home page, shop, every category and brand, the 60 best-selling products, blog and CMS pages. The same is available as
php artisan sellora:cache-warm(add-vto list the pages). The built-inphp artisan serveserver answers one request at a time and cannot warm itself; use the command there. - Clear page cache: drops every cached page and fragment.
- Optimize: runs
config:cache,route:cacheandevent:cache(php artisan optimizein a terminal also precompiles the views). Run it again after editing.envor updating Sellora. Clear optimized files undoes it. - Recommendations: what to change on this server for more speed.
Server checklist#
OPcache#
OPcache keeps compiled PHP in memory and makes every request two to four times faster. Most hosts enable it; check Settings > Performance > Cache status. Recommended php.ini values:
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
; optional, PHP 8: JIT
opcache.jit=tracing
opcache.jit_buffer_size=64M
With opcache.validate_timestamps=0 (fastest) you must restart PHP-FPM after every update or file change.
Optimize after every update#
php artisan optimize # config, routes, events, views
php artisan optimize:clear # undo (also clears the page cache)
.env is no longer read once the configuration is cached: after any .env change, run php artisan optimize again (or click Optimize).
Composer#
If you install or update the dependencies with Composer yourself, use the optimized, production autoloader:
composer install --no-dev --optimize-autoloader --classmap-authoritative
Redis (optional)#
For busy stores on a VPS, Redis is faster than files for the cache and sessions. Install Redis and the phpredis extension, then set in .env:
CACHE_STORE=redis
SESSION_DRIVER=redis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
and run php artisan optimize. The page cache works the same way with Redis (invalidation uses a version key, not tags). Early hits need the file driver; with Redis, cached pages are answered by the application after a minimal start (still about ten times faster than building the page).
Queue (optional)#
By default e-mails are sent while the customer waits (QUEUE_CONNECTION=sync). To send order and newsletter e-mails in the background, set QUEUE_CONNECTION=database in .env and run php artisan optimize. The cron job then runs queue:work --stop-when-empty every minute. Order e-mails are only sent after the order is saved.
Apache#
public/.htaccess already contains a block labelled C1 performance headers: long-lived immutable caching for files requested with a version (?v=…), 1 day for other CSS/JS, 30 days for images and fonts, and gzip compression for HTML, CSS, JS, JSON and SVG (needs mod_headers, mod_deflate).
Nginx#
gzip on;
gzip_types text/plain text/css application/javascript application/json application/xml image/svg+xml;
location ~* \.(css|js|mjs|woff2?|ttf|otf|svg|png|jpe?g|gif|webp|avif|ico)$ {
if ($arg_v) { add_header Cache-Control "public, max-age=31536000, immutable"; }
expires 30d;
access_log off;
try_files $uri =404;
}
Do not add fastcgi_cache for the whole site: personal pages must never be shared. Sellora's own page cache already knows who may receive a cached copy.
Troubleshooting: "I changed X and the storefront did not update"#
- Check who you are. Signed in as admin? You always see fresh pages; check in a private window instead, where you are a normal visitor.
- Look at the header. In the browser's developer tools (Network tab, the document request) find
X-Sellora-Cache.HITmeans a cached copy;MISSorBYPASSmeans it was built now. - You edited a file (Blade view, theme CSS,
config/*.php,.env): files are not watched. Click Clear page cache, and Optimize if you changed config or.env. After editing theme files,php artisan view:cleartoo. - You changed the database directly (phpMyAdmin, SQL import): Sellora cannot see that. Click Clear page cache.
- A time-based change (sale start, special price date): it appears after at most Keep pages for minutes.
- Stock shows the old number: stock changes from orders refresh with the page lifetime; editing the product in the admin refreshes at once.
- Still old after clearing: a CDN or proxy in front of the store (Cloudflare "Cache Everything", Varnish) may hold its own copy. Purge it there, and bypass its cache when the cookies
sl_ncorsellora-session(the store session) are present. - Everything else fails: turn Enable the full-page cache off, check that the problem is gone, and contact support with the page address.