# Server Configuration

This document covers web server configuration for serving Capell's static HTML cache, recommended settings for production, and development environment setup.

---

## Overview

Capell's frontend generates static HTML files for every published page and stores them in `public/page-cache/`. For maximum performance, configure your web server to check for and serve these files directly — bypassing PHP entirely. This means TTFB (time to first byte) is determined by disk I/O and your server, not PHP execution time.

If no cached file exists (e.g. for a page that hasn't been generated yet, or after a cache purge), the request falls through to PHP as normal and Capell generates the page dynamically, then queues a job to save the cached version.

Capell refuses to write a public HTML cache file when the rendered response contains explicit authoring markers, field paths, model IDs, or signed admin editor URLs. Those responses are sent with `Cache-Control: private, no-store` and `X-Frontend-Cache: BYPASS`. Treat this as a deployment blocker: fix the Blade/theme/package output rather than trying to force the cache write.

---

## Apache Configuration

Add these rules to your `.htaccess` file, before the standard Laravel rewrite rules:

```apache
# Serve static HTML cache if the file exists
RewriteCond %{DOCUMENT_ROOT}/page-cache%{REQUEST_URI}.html -f
RewriteRule ^ page-cache%{REQUEST_URI}.html [L]
```

**Full `.htaccess` example:**

```apache
<IfModule mod_rewrite.c>
    RewriteEngine On

    # Serve cached HTML if it exists
    RewriteCond %{DOCUMENT_ROOT}/page-cache%{REQUEST_URI}.html -f
    RewriteRule ^ page-cache%{REQUEST_URI}.html [L]

    # Standard Laravel rewrite
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^ index.php [L]
</IfModule>
```

---

## Nginx Configuration

Add the static HTML check to your `location /` block:

```nginx
location / {
    try_files $uri $uri/ /page-cache$uri.html /index.php?$query_string;
}
```

**Full server block example:**

```nginx
server {
    listen 80;
    server_name example.com;
    root /var/www/html/public;

    index index.php;

    location / {
        try_files $uri $uri/ /page-cache$uri.html /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }
}
```

---

## Cache Management

### Clearing the cache

After making bulk changes or restoring from a database backup:

```sh
# Clear just the HTML page cache
php artisan capell:html-cache:clear

# Clear everything (config, views, routes, page cache)
php artisan optimize:clear
```

From the admin panel, use the **Clear Cache** button in the top navigation bar.

### Selective cache invalidation

Capell automatically purges cache entries when content changes are published. The following events trigger targeted invalidation:

- Publishing a page purges that page's cached HTML.
- Updating a page's slug purges the page, all its descendants, and any listing pages.
- Updating site settings, translations, themes, or media purges cached URLs that used those records.
- Site setting changes also match cached URLs by the site's configured domains, so root pages such as the homepage are cleared even if the model index missed an explicit `Site` dependency.
- Changes to global navigation purge pages that render navigation.

### Regenerating the cache

After a full clear, regenerate the cache:

```sh
# Regenerate all sites
php artisan capell:static-site

# Regenerate for one site
php artisan capell:static-site --site=1
```

### Lockdown cache swap

Lockdown is designed for incidents where PHP middleware alone is not enough because the web server may serve `public/page-cache` directly. When Lockdown is enabled, Capell renames the live page cache directory to a preserved sibling, creates a fresh `page-cache` directory, and writes Lockdown HTML into the same cached paths. When Lockdown is disabled, the temporary Lockdown cache is removed and the preserved live cache is moved back.

Do not clear `public/page-cache` during Lockdown unless you are deliberately accepting a cold cache rebuild after recovery. See [Lockdown](../../../docs/operations/lockdown.md) for the operator runbook.

---

## Filesystem Disk

The page cache uses a dedicated storage disk. Ensure it is configured in `config/filesystems.php`:

```php
'disks' => [
    'page_cache' => [
        'driver' => 'local',
        'root' => public_path('page-cache'),
        'throw' => false,
    ],
],
```

The `public/page-cache/` directory must be writable by the web server process.

---

## Development Environment

To skip caching in development, add to your `.env`:

```ini
DEBUG_SKIP_CACHE=true
CAPELL_HTML_CACHE=false
HTML_MINIFY=false
```

`DEBUG_SKIP_CACHE=true` bypasses cache reads and writes on every request without disabling the cache system. This is the simplest and safest option for local development.

---

## Performance Optimisations

- Enable HTTP/2 on your server to allow multiplexed requests.
- Set appropriate cache headers for static assets (CSS, JS, images) — these should be long-lived.
- Consider a CDN in front of your server to serve cached HTML from edge locations.
- Use `opcache` for PHP to speed up the dynamic fallback path.
- For very high-traffic sites, consider a Varnish or Nginx proxy cache in front of the origin as a second caching layer.

---

## Further Reading

- [Guide](../../../docs/frontend/guide.md) — HTML cache internals, cache invalidation, Tailwind setup
- [Installation Guide](../../../docs/getting-started/install.md) — queue setup, filesystems disk, logging
- [Artisan Commands](../../../docs/development/artisan-commands.md) — `capell:static-site`, `capell:html-cache:clear`
- [Page & Site Loading](page-site-loading.md) — how the frontend request pipeline works