Search by

trail / freeze

dodgycoffee

Static site cache for trail

Package info

source.tube/brindly/trail-freeze

Issues

Type:trail-addon

pkg:composer/trail/freeze

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

dev-main / 0.1.x-dev 2026-09-20 23:55 UTC

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

  • GET request + no cookie bypass (explained in config)
  • response is 200 with text/html
  • response sets no cookie and no Cache-Control: no-store
  • path isnt in skip config 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