Brewless Brewless Docs

Running your application

Edge cache

Pages that are the same for every visitor are kept at bunny.net, so the web container can scale to zero between visits.

On this page

The web container of an environment sleeps when nobody needs it: Scaleway scales it to zero after 15 minutes without a request, and every request that reaches it starts those 15 minutes again. Behind an edge that keeps nothing, every visitor and every crawler reaches the container, and a public site hardly ever sleeps. So bunny.net keeps the pages that are the same for everybody, and answers the next request for them itself.

What decides

The application decides, with Cache-Control. A pull zone Brewless manages follows what the application says and keeps nothing it was not allowed to keep. It keeps a page per hostname: one pull zone answers for your domain, every subdomain and the test address, and the page of example.com/ is never what shop.example.com/ gets. Every deploy checks that, and turning it on for a pull zone made earlier empties that pull zone. brewless/laravel (from version 0.17) gives an answer Cache-Control: public, max-age=0, s-maxage=300 when it cannot depend on who asked:

  • the request is a GET or HEAD without cookies and without Authorization, and not for /_ops/*;
  • the answer is a 200, 301, 308, 404 or 410, sets no cookie and started no session;
  • the application did not set Cache-Control itself;
  • it varies on nothing but Accept-Encoding.

s-maxage is for the edge, max-age=0 keeps the page out of the browser. Every release empties the edge, so after a deploy nobody gets a page of the release before, and between deploys a page is at most s-maxage seconds old. BREWLESS_EDGE_CACHE sets the seconds; 0 keeps nothing:

brewless.yml
environments:
  production:
    variables:
      BREWLESS_EDGE_CACHE: "3600"

Pages without a session

A route in Laravel's web group starts a session, and the session sets a cookie on every answer: such a page is never kept. A page with no form that shows nothing of the signed-in visitor does not need it. Take the session off its route:

routes/web.php
use Illuminate\Foundation\Http\Middleware\PreventRequestForgery;
use Illuminate\Session\Middleware\StartSession;
use Illuminate\View\Middleware\ShareErrorsFromSession;

Route::withoutMiddleware([StartSession::class, ShareErrorsFromSession::class, PreventRequestForgery::class])->group(function () {
    Route::view('/', 'home');
    Route::get('/blog/{post}', [BlogController::class, 'show']);
});

On Laravel 12 the CSRF middleware is ValidateCsrfToken. A page with a form of its own keeps the session. A form a script sends with fetch() passes Laravel 13's CSRF check on Sec-Fetch-Site: same-origin without a token.

Warning

The edge keeps one answer per hostname and address and does not look at Vary. A page that answers differently on something else than its address (the visitor's IP address, a header of your own, Accept, a cookie it reads without a session) must set Cache-Control itself: ->middleware('cache.headers:private'), or no-store. Give another version of a page, such as its Markdown, an address of its own.

Checking it

Ask for a page twice. The second answer comes from the edge:

curl -sI https://example.com/pricing | grep -i -E 'cache-control|cdn-cache'
curl -sI https://example.com/pricing | grep -i -E 'cache-control|cdn-cache'

cdn-cache: HIT on the second. Still MISS, with cache-control: no-cache, private: the page set a cookie or a session, or the request carried a cookie. How long the container ran and slept is on the environment's page, under serverless usage; see Scaling.