trail / freeze
Static site cache for trail
Requires
- php: >=8.4
- trail/framework: ^0.1
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is not auto-updated.
Last update: 2026-09-21 00:24:25 UTC
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
Query strings are ignored for reasons. /about, /about/, and /about?utm=whatever would all be one page at about/index.html
exclusions
// trail/config/freeze.php
'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
Trail doesn't have an auth system out of the box so this is more like "if you did a thing" guidance. If a request has a cookie from the cookies config list, it'll just skip the cache. So you can toss in a session cookie there and have logged in users get uncached responses
'cookies' => ['trail_session'],
A hypothetical auth addon can also just register its own using boot()
Freeze::bypass('trail_session');
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/freeze.php, optional]
return [
'*' => [
'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*'],
'cookies' => [],
'pages' => static fn (): array => [],
],
'dev' => ['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
map $request_method $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
@frozen {
method GET HEAD
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