trail / freeze
Static site cache for trail
Requires
- php: >=8.4
- trail/framework: ^0.3
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Static site cache for trail framework.
install
composer require trail/freeze
It works. Now every public page freezes on first visit, and thaws when you tell it to
what gets frozen
This is how its determined what to freeze
GETrequest + no cookie bypass (explained in config)- response is
200withtext/html - response sets no cookie and no
Cache-Control: no-store - path isnt in
skipconfig list - no query string
- folder isnt full (
perFolderoption)
/about and /about/ would both be about/index.html. Anything with a query string skips the cache, so /posts?page=2 never hits page 1
exclusions
'skip' => ['/admin*', '/pastebin*', '/search'],
Orrrr you can do it per route just cause
use Trail\Freeze\Gates\NeverFreeze;
Route::get('/form', ShowForm::class)->gate(NeverFreeze::class);
Route::group('/admin', [NeverFreeze::class], static function (): void {
...
});
You can also do this if headers are your thing: Response::html($body)->header('Cache-Control', 'no-store')
logged in users
if a request has a cookie from cookies config list, it'll skip the cache. trail/sessions registers its own cookie. anyone with a session (signed in or not) gets uncached responses. auth pages send no-store so they never freeze
'cookies' => ['my_cookie'],
Other addons can register their own using boot()
Freeze::bypass('my_cookie');
deletus
use Trail\Freeze\Freeze;
Freeze::thaw(); // everything
Freeze::thaw('/posts/hello'); // one page
Freeze::thaw('/posts/*'); // glob
Cli
trail/trail freeze::clear
trail/trail freeze::clear /posts/*
If you were to make say... a cms, you could call thaw() on save for an entry and pages attached to it and it would invalidate them all on
Expired pages refreeze on next hit automatically so nothing to do there
manual freeze
Cli
trail/trail freeze::build
trail/trail freeze::build --clear
not cli
'pages' => static fn (): array => array_map(
static fn (string $slug): string => '/posts/' . $slug,
Posts::slugs(),
),
For a full static deploy you can just build the freez + copy the folder wherever
config [trail/config/app.php, optional]
Lives in the freeze section of app.php
return [
'*' => [
'freeze' => [
'enabled' => true, // turn it on/off
'dir' => null, // storage/cache/cooler
'expires' => null, // seconds. null is forever
'stamp' => 'cached at {time}, baba booey', // stamp at end. {time}, {path}, null for none
'skip' => ['/admin*'],
'perFolder' => 1000, // freeze cap per folder. null for no cap
'cookies' => [],
'pages' => static fn (): array => [],
],
],
'dev' => ['freeze' => ['enabled' => false]],
];
web server in charge
Out of the box trail serves the cached files directly. Less performant, but works with no fiddling and does have a measurable benefit. But its still hitting php every time.
If you wanna skip php completely, you can configure a few things to let caddy/nginx serve the files directly
nginx
'dir' => Config::get('dir.project') . '/web/cooler', // or whatever
nginx conf
# frozen pages only for GET/HEAD with no query string
map "$request_method:$args" $frozen {
default "/nowhere";
"GET:" "/cooler";
"HEAD:" "/cooler";
}
server {
server_name example.com;
root /var/www/example/web;
index index.php;
# file, frozen page, then trail
location / {
try_files $uri $frozen$uri/index.html /index.php$is_args$args;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
}
caddy
Caddy is cooler it can read the files from the default location. Nginx is more work.
caddyfile
example.com {
root * /var/www/example/web
# frozen page for GET/HEAD with no query string
@frozen {
method GET HEAD
expression {query} == ""
file {
root /var/www/example/trail/storage/cache/cooler
try_files {path}/index.html
}
}
handle @frozen {
root * /var/www/example/trail/storage/cache/cooler
rewrite * {path}/index.html
file_server
}
# everything else is trail
php_fastcgi unix//run/php/php8.4-fpm.sock
file_server
}
expires and cookies only work when trail is serving cache (cause php checks it). But you can run freeze::clear on a cron to replace expirations, and probably some config tweaking to handle cookie stuff