# 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.


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:

```yaml 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:

```php 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:

```bash
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](/running/scaling).
