# Critical Asset Optimization

> **Who is this for?**
> Frontend developers tuning Largest Contentful Paint (LCP) and page-load speed via critical asset registration, preload hints, and DNS prefetch.

> **TL;DR:** `CriticalAssetRegistry` lets you mark stylesheets and scripts as critical or deferred; `AssetOptimizationMiddleware` injects resource hints (`<link rel="dns-prefetch">`) into HTML responses to accelerate asset delivery.

---

## When to use this

Register assets when:

- You have a critical stylesheet (e.g. theme CSS) that should be preloaded to reduce render-blocking delays
- You have third-party scripts or fonts that benefit from DNS prefetch hints
- You want to defer non-critical JavaScript loads (e.g. insights, tracking)

The registry pairs with the asset optimization middleware to inject browser hints that signal the browser to resolve DNS, establish connections, or preload resources _before_ they're explicitly requested in the HTML.

## How it's wired

`CriticalAssetRegistry` is registered as a **singleton** in the Laravel container under both `CriticalAssetRegistry::class` and the alias `'capell-frontend.critical-assets'` (see [`packages/frontend/src/Providers/FrontendServiceProvider.php`](../../packages/frontend/src/Providers/FrontendServiceProvider.php) line 150–151).

`AssetOptimizationMiddleware` is **not auto-applied** to all responses. It is imported in the frontend service provider but registered only as a route middleware alias. You must explicitly apply it to routes where you want asset optimization hints injected.

To enable it on a route group:

```php
Route::middleware(['frontend.asset-optimization'])->group(function () {
    // routes here will have asset hints injected
});
```

(Check your routes configuration for the exact alias name; the middleware class must be aliased in `FrontendServiceProvider::registerMiddlewareAliases()`.)

## Public API (CriticalAssetRegistry)

| Method                                                                      | Returns  | Purpose                                                                           |
| --------------------------------------------------------------------------- | -------- | --------------------------------------------------------------------------------- |
| `registerCritical(string $handle, string $url, string $type = 'css'): void` | `void`   | Register a critical asset (defaults to CSS)                                       |
| `registerDeferred(string $handle, string $url, string $type = 'js'): void`  | `void`   | Register a deferred asset (defaults to JavaScript)                                |
| `getCriticalAssets(): array`                                                | `array`  | Get all registered critical assets as `[handle => ['url' => ..., 'type' => ...]]` |
| `getDeferredAssets(): array`                                                | `array`  | Get all registered deferred assets with same structure                            |
| `criticalCss(): string`                                                     | `string` | Render all critical CSS assets as HTML `<link>` tags                              |
| `deferredScripts(): string`                                                 | `string` | Render all deferred JS assets as HTML `<script defer>` tags                       |
| `clear(): void`                                                             | `void`   | Clear both critical and deferred asset registries                                 |

## Rendering critical and deferred assets in templates

The registry provides two helper methods to render registered assets directly in your Blade layout:

**`criticalCss()`** — Iterates over all critical assets, filters for `type === 'css'`, and renders each as a `<link rel="stylesheet">` tag. Call this in your layout's `<head>` to include critical stylesheets:

```blade
{{ CriticalAssetRegistry::criticalCss() }}
```

**`deferredScripts()`** — Iterates over all deferred assets, filters for `type === 'js'`, and renders each as a `<script defer>` tag. Call this in your layout (typically before `</body>`) to load deferred scripts:

```blade
{{ CriticalAssetRegistry::deferredScripts() }}
```

Both methods filter by type, so you can register multiple types (e.g. `'font'`, `'image'`) and only CSS and JS will be rendered. If you register other types, you must handle their rendering separately.

## Allowed asset types

The registry stores assets with a `type` field. Two type conventions are used:

- **`'css'`** — CSS stylesheet (default for `registerCritical()`)
- **`'js'`** — JavaScript file (default for `registerDeferred()`)

The types are stored as plain strings and consumed by the helper methods `criticalCss()` and `deferredScripts()`, which filter by type. You can technically register other types (e.g. `'font'`, `'image'`), but they will be ignored by the built-in render methods unless you add custom logic.

## Example — registering assets

```php
<?php

declare(strict_types=1);

namespace App\Providers;

use Capell\Frontend\Support\Assets\CriticalAssetRegistry;
use Illuminate\Support\ServiceProvider;

final class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        CriticalAssetRegistry::registerCritical(
            handle: 'theme-stylesheet',
            url: '/css/theme.css',
            type: 'css',
        );

        CriticalAssetRegistry::registerDeferred(
            handle: 'insights',
            url: 'https://example.com/insights.js',
            type: 'js',
        );

        CriticalAssetRegistry::registerDeferred(
            handle: 'tracking',
            url: '/js/tracking.js',
            type: 'js',
        );
    }
}
```

Since `CriticalAssetRegistry` uses static methods, you can register assets from any service provider's `boot()` method. **Always register in `boot()`, not `register()`**, to ensure all packages and configuration have loaded.

## Behavior (AssetOptimizationMiddleware)

When a response passes through `AssetOptimizationMiddleware`:

1. **Checks skip conditions**: if the response is not HTTP 200 or does not have `Content-Type: text/html`, the middleware passes the response through unchanged.

2. **Fetches context**: reads the current `FrontendContext` to access the theme.

3. **Injects DNS prefetch hints**: if the theme has an `assetUrl` attribute, injects `<link rel="dns-prefetch" href="...">` pointing to that domain into the `</head>` tag.

4. **Graceful fallback**: if the frontend context is unavailable (exception caught), the middleware silently skips optimization and returns the response as-is.

The middleware **injects DNS prefetch hints only**. It does not render asset tags; preload hints for critical CSS and defer tags for scripts are handled by the registry's `criticalCss()` and `deferredScripts()` methods, which you call in your Blade layout (see "Rendering critical and deferred assets in templates" above).

## Gotchas

- **Register in `boot()`, not `register()`**: The registry must be populated before views are rendered, so all registrations must happen in the service provider's `boot()` method.

- **Middleware is opt-in**: The asset optimization middleware is a route alias, not globally applied. You must explicitly attach it to route groups where you want resource hints injected.

- **Static methods**: `CriticalAssetRegistry` uses static methods and a static registry. This is not testable in isolation — clear the registry with `CriticalAssetRegistry::clear()` between tests if needed.

- **Fonts and resource hints**: If you register fonts with type `'font'`, the render methods will ignore them. You must handle font preload/crossorigin hints separately in your layout or extend the middleware to support font types.

## Related

- [Performance index](README.md)
- [Fragment caching](fragment-caching.md)
- [ETag & conditional responses](etag-and-conditional-responses.md)
- [Cache invalidation](cache-invalidation.md)