rias / craft-static-cache
Full-page static caching for Craft CMS.
Package info
github.com/riasvdv/craft-static-cache
Type:craft-plugin
pkg:composer/rias/craft-static-cache
Requires
- php: ^8.5
- ext-uri: *
- craftcms/cms: ^6.0.0@dev
Requires (Dev)
- carthage-software/mago: ^1.26
- nunomaduro/collision: ^8.1.1
- orchestra/testbench: ^11.0
- pestphp/pest: ^4.0
- pestphp/pest-plugin-arch: ^4.0
- pestphp/pest-plugin-laravel: ^4.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-31 09:14:35 UTC
README
Full-page static HTML caching for Craft CMS 6.
This package stores eligible HTML responses in Laravel's cache and can optionally publish the rendered HTML to disk. When disk publishing is enabled, your web server can serve cached pages directly before PHP boots.
Installation
You can install the package via Composer:
composer require rias/craft-static-cache
Install the Craft plugin:
php craft plugin/install static-cache
Configuration
You can publish the config file with:
php craft vendor:publish --tag=static-cache
This will publish the config file to config/craft/static-cache.php.
The published config file looks like this:
<?php use CraftCms\Cms\Cms; return [ 'enabled' => true, 'store' => null, 'publish' => false, 'disk' => [ 'path' => public_path('static'), 'permissions' => Cms::config()->defaultDirMode, ], ];
The most important options are:
enabled: enables full-page static caching.store: the Laravel cache store used for canonical cached responses. Usenullfor the default store.publish: also write cached HTML to disk for web server delivery.disk.path: the public directory where static HTML files are written.queryParameters.allow: query parameters that should remain part of the cache key when query parameters are ignored by default.exclude.uriParts: route patterns that should never be cached.invalidation.rules: route patterns that should be cleared when related elements change.replacers: classes that implementRias\CraftStaticCache\Contracts\Replacer.warming.urls: extra same-site URLs to warm in addition to live entry URLs.warming.concurrency: number of concurrent HTTP requests used by the warm command.warming.timeout: warm request timeout in seconds.warming.headers: headers sent with every warm request.
Usage
Once enabled is set to true, the package will cache anonymous GET site requests that return a publicly cacheable HTML response. HEAD requests can read existing entries but never create them.
Requests are not cached when they are action requests, preview requests, tokenized requests, authenticated requests, or match an excluded URI pattern. Responses are not cached when they are not 200 responses, set cookies, are non-HTML responses, or include private, no-store, or no-cache in Cache-Control. Laravel responses default to private; explicitly mark public pages with Cache-Control: public in the response or in middleware that runs inside the static cache middleware.
Cached entries are tagged with Craft element cache dependencies and expire at the earliest collected Craft deadline. When Craft invalidates element caches, related static cache entries are cleared as well. A render that overlaps an invalidation is not cached.
The locks.store cache store must support atomic locks and be shared by all web and worker processes. Without atomic locks, new responses are not cached. Use the same store configuration on every application instance.
Publishing Static Files
Set publish to true to write cached HTML to disk:
'publish' => true, 'disk' => [ 'path' => public_path('static'), ],
Published files follow the following format:
public/static/{host}{request-path}/index_{query-string}.html
For example, /news?page=2 on example.test is written to public/static/example.test/news/index_page=2.html. The homepage is written to public/static/example.test/index_.html.
Responses with a collected expiry deadline or content transformed by a replacer, including CSRF placeholders, are cached only in PHP. They are not published because direct static delivery cannot enforce expiry or restore per-request values. Published files are replaced atomically so readers receive either the complete old response or the complete new response.
When upgrading from the previous filename layout, clear existing static cache entries and remove any remaining old published files before enabling the updated rewrite rules. Canonical response keys are versioned, so previous payloads are not reused. Apply application security headers at the web-server level as well when serving HTML directly from disk.
Server Rewrite Rules
Place the rewrite rule before the normal Laravel/Craft front controller fallback.
Use the query-ignoring rules when queryParameters.ignoreByDefault is true and queryParameters.allow is empty. Use the query-aware rules when query parameters should be part of the cache key.
Nginx
Use this when query parameters are ignored:
set $try_location @static; if ($request_method != GET) { set $try_location @not_static; } location / { try_files $uri $try_location; } location @static { try_files /static/${host}${uri}/index_.html $uri $uri/ /index.php?$args; } location @not_static { try_files $uri /index.php?$args; }
Use this when query parameters are part of the cache key:
set $try_location @static; if ($request_method != GET) { set $try_location @not_static; } location / { try_files $uri $try_location; } location @static { try_files /static/${host}${uri}/index_$args.html $uri $uri/ /index.php?$args; } location @not_static { try_files $uri /index.php?$args; }
Apache
Use this when query parameters are ignored:
RewriteCond %{DOCUMENT_ROOT}/static/%{HTTP_HOST}%{REQUEST_URI}/index_\.html -s RewriteCond %{REQUEST_METHOD} GET RewriteRule .* static/%{HTTP_HOST}%{REQUEST_URI}/index_\.html [L,T=text/html]
Use this when query parameters are part of the cache key:
RewriteCond %{DOCUMENT_ROOT}/static/%{HTTP_HOST}%{REQUEST_URI}/index_%{QUERY_STRING}\.html -s RewriteCond %{REQUEST_METHOD} GET RewriteRule .* static/%{HTTP_HOST}%{REQUEST_URI}/index_%{QUERY_STRING}\.html [L,T=text/html]
If you change disk.path, update the /static prefix in these rules to match the public path your server can access.
When query parameters are ignored, all query-string variants can resolve to the same published file before PHP boots. When query parameters are part of the cache key, the web server can only serve the file directly when the raw query string matches the normalized cached query string. Other query-string variants fall through to PHP and can still be served from the application cache.
Invalidation
Craft Static Cache tracks the element cache tags collected while a page is rendered. When Craft invalidates element caches, cached pages with matching dependency tags are cleared automatically. This removes both the Laravel cache entry and any published static file.
Entry changes can also clear related pages through invalidation rules. Rule keys usually match section handles, so saving an entry in the news section can clear listing pages like /news and /news/*.
'invalidation' => [ 'rules' => [ 'news' => [ 'news', 'news/*', ], 'all' => [ '/', 'blog/*', ['uriParts' => ['products/', ['slug']]], ], ], ],
String patterns support * wildcards. Array patterns use Craft route-token URI parts.
Replacers
Replacers can transform dynamic fragments before storage and after retrieval. They must implement Rias\CraftStaticCache\Contracts\Replacer.
use Rias\CraftStaticCache\Contracts\Replacer; use Rias\CraftStaticCache\Data\CacheContext; class MyReplacer implements Replacer { public function prepare(string $html, CacheContext $context): string { return $html; } public function replace(string $html, CacheContext $context): string { return $html; } }
Register replacers in the config file:
'replacers' => [
MyReplacer::class,
],
Cache Warming
Warm the cache before visitors hit uncached pages:
php craft static-cache:warm
The warm command makes real same-site GET requests, so normal Craft routing, middleware, response eligibility, replacers, tags, and optional static file publishing all still apply. It warms live Entry URLs across enabled sites, plus any extra URLs configured in warming.urls.
'warming' => [ 'urls' => [ '/', '/news', '/contact', ], 'concurrency' => 10, 'timeout' => 10, 'headers' => [ 'X-Cache-Warmer' => 'deploy', ], ],
Relative extra URLs are expanded against every enabled site. Absolute extra URLs must match a configured site's HTTP(S) scheme, host, port, and base path. External URLs are ignored. Redirects are not followed, even within the same site, so warming headers cannot leak to redirect destinations. Configure the final URL instead.
By default, warming skips a URL only when its canonical cache payload still exists. An index row left behind after cache eviction does not prevent warming. Concurrency and timeout options must be positive integers.
Useful command options:
php craft static-cache:warm --all php craft static-cache:warm --include="news/*" --exclude="news/private/*" php craft static-cache:warm --concurrency=5 --timeout=3 php craft static-cache:warm --header="X-Cache-Warmer: deploy" php craft static-cache:warm --user=deploy --password=secret php craft static-cache:warm --insecure
Failed warm requests are reported as warnings, but the command still exits successfully.
Commands
You can inspect and clear the cache from the command line:
php craft static-cache:status php craft static-cache:warm php craft static-cache:clear php craft static-cache:clear-url /news php craft static-cache:clear-tags static-cache
The same status information is also registered with Laravel's about command:
php craft about
The package also registers a Craft Clear Caches option for clearing static cache entries from the control panel.
Testing
composer test
Changelog
Please see GitHub Releases for more information on what has changed recently.
Credits
License
The MIT License (MIT).