Skip to content

Frontend Optimizer — Improvement & Growth Plan

Package: capell-app/frontend-optimizer · Kind: package · Tier: premium · Product group: Capell Foundation · Bundle: foundation · Status: Complete

Frontend Optimizer overrides Capell’s FrontendAssetManifestRenderer contract to convert the public <head> asset manifest into a layout-scoped render profile (a sha256 of asset list + context + critical-CSS settings), then defers non-critical CSS/JS and inlines generated above-the-fold CSS. The live entry point is real: vendor/capell-app/frontend/resources/views/components/app/head/index.blade.php calls app(FrontendAssetManifestRenderer::class)->render(...), the optimizer rebinds that contract as a singleton at boot (src/Providers/FrontendOptimizerServiceProvider.php:69), and CapellFrontendAssetManifestRenderer falls back to the default renderer on any throwable. Critical CSS is generated out-of-band by GenerateCriticalCssJobGenerateCriticalCssActionPlaywrightCriticalCssGenerator, which shells out via symfony/process to resources/js/generate-critical-css.mjs (real Chromium, two viewports). BuildOptimizedImageMarkupAction now provides a package-owned responsive image seam for lazy/eager images, width candidates, and AVIF/WebP <picture> source candidates after callers provide hydrated media URLs. Surfaces: admin + frontend. Models/tables: FrontendRenderProfile + FrontendOptimizationRun (frontend_render_profiles, frontend_optimization_runs). Deps: lorisleiva/laravel-actions, spatie/laravel-data, spatie/laravel-package-tools, symfony/process; JS dep playwright ^1.59.1. Marketplace summary now leads with faster first paint and real-render critical CSS. capell.json declares the extension card plus four committed Capell screenshot-runner PNGs for profile asset output and critical-CSS output in light and dark modes.

  1. Done/Shipped: real critical health probes. FrontendOptimizerHealthCheck::runDiagnostics() now asserts the public asset-manifest renderer is bound to the optimizer, render-profile manifest storage is writable, generated critical-CSS storage is writable, Node/Playwright generator prerequisites are present, and automatic generation is not configured to run on the synchronous queue. Evidence: tests/Unit/Health/FrontendOptimizerHealthCheckTest.php covers healthy diagnostics plus renderer, Node, script, Playwright dependency, and sync-queue failures. — src/Health/FrontendOptimizerHealthCheck.php — M

  2. Done/Shipped: optimization scope selection is wired into the live renderer. CapellFrontendAssetManifestRenderer now resolves the configured CriticalCssSettings::scope() through ResolveOptimizationScopeAction and shapes the profile signature context accordingly: layout scope uses layout/theme, render-profile scope keeps the full context, and page-url scope adds the current URL. — src/Actions/ResolveOptimizationScopeAction.php, src/Support/CapellFrontendAssetManifestRenderer.php — M

  3. Remove the dead debug_query_support setting or implement it. CriticalCssSettings::debugQuerySupportEnabled() (src/Support/CriticalCssSettings.php:91) is defined, exposed as a Filament toggle, seeded in settings migration, and documented — but never read anywhere in src. It is pure surface-area noise that implies a debugging capability that does not exist. — src/Support/CriticalCssSettings.php, src/Filament/Settings/FrontendOptimizerSettingsSchema.php, database/settings/2026_05_24_000001_add_frontend_optimizer_settings.php — S

  4. Fix the PersistRenderProfileAction critical_css_path reset-on-create only. On createOrFirst for an existing profile, the update branch fills label/manifest/scope/signature but never touches critical_css_path or status — correct. But on the create branch the seeded values array hardcodes 'critical_css_path' => null; that is fine for a brand-new row, yet status is hardcoded to Pending on create while the update branch leaves status untouched, meaning a signature-stable profile that previously Failed keeps Failed and never re-pends. Combined with shouldDispatchGeneration only re-dispatching on Failed, this is intended — but document the state machine or add a test, because the create/update asymmetry is a latent bug magnet. — src/Actions/PersistRenderProfileAction.php:17-46 — S

  5. Done/Shipped: critical eligibility now follows manifest hints. CapellFrontendAssetManifestRenderer now reads optional manifest requirement hints such as criticalEligible, critical_eligible, and frontendOptimizerCriticalEligible, plus package-name hints, before falling back to the legacy foundation-theme:css compatibility path. Non-Foundation assets can opt into critical CSS without string-literal coupling. Evidence: tests/Unit/Support/CapellFrontendAssetManifestRendererTest.php. — src/Support/CapellFrontendAssetManifestRenderer.php — M

  6. Done/Shipped: JS loading now follows manifest hints. The renderer now prefers per-asset optimizer loading-strategy hints and core presentation loading strategies (Idle, Interaction, Visible) before falling back to the legacy foundation-theme:runtime idle behavior. This keeps existing Foundation output stable while letting other packages request idle/intersection/lazy behavior through manifest data. Evidence: tests/Unit/Support/CapellFrontendAssetManifestRendererTest.php. — src/Support/CapellFrontendAssetManifestRenderer.php — M

  7. Done/Shipped: stale profile GC is available. PruneRenderProfilesAction and capell:frontend-optimizer:prune-profiles prune render profiles older than a retention window, remove their manifest and generated critical-CSS files from local storage, and rely on the existing FK cascade to delete optimization runs. The command supports dry-run, JSON, retention days, and limit options, and the manifest now declares the console surface and prune command. Evidence: tests/Unit/Actions/PruneRenderProfilesActionTest.php. — src/Actions/PruneRenderProfilesAction.php, src/Console/Commands/PruneRenderProfilesCommand.php, capell.json — M

  8. composer.json PHP floor (^8.3) contradicts repo standard (8.4). The skill states PHP 8.4 / typed constants; RenderProfileAssetRenderer already uses a typed private const string. Align the constraint to avoid a package that won’t install on the documented baseline mismatch the other way. — composer.json:7 — S

Manifest capabilities[]: frontend-optimizer, frontend-optimizer-frontend, frontend-assets, cache-blocking.

  • Asset minification / bundling — absent (table stakes). The package re-emits the existing manifest URLs verbatim (assetUrl() just resolves the Vite/public path). There is no concatenation, no minify, no HTTP/2-aware bundling. “Profile-based CSS and JavaScript delivery” implies optimization the code does not perform beyond load-strategy rewriting. Either build it or stop implying it in copy.
  • Done/Shipped: image optimization seam. BuildOptimizedImageMarkupAction returns safe <picture> / <img> markup with configurable width candidates, AVIF/WebP source candidates, lazy/eager loading, async decoding, sizes/srcset attributes, and high-priority eager images. It intentionally accepts already-hydrated URLs instead of querying media records from public Blade, and capell.json now advertises frontend-optimizer-images plus a media-library support pairing. — src/Actions/BuildOptimizedImageMarkupAction.php, config/capell-frontend-optimizer.php, tests/Unit/Actions/BuildOptimizedImageMarkupActionTest.php, tests/Unit/FrontendOptimizerManifestTest.php
  • Done/Shipped: manifest-driven preload/fetchpriority/font hints. The optimizer now carries manifest preloads into render-profile resource_hints, includes them in the profile hash/manifest, and renders escaped <link> resource hints before optimized assets. Font preload hints can declare as=font, type, and crossorigin; LCP image hints can carry fetchpriority=high; modulepreload is preserved. Remaining future depth: automatic preconnect/dns-prefetch origin discovery and font-display/subsetting. — src/Data/FrontendResourceHintData.php, src/Support/CapellFrontendAssetManifestRenderer.php, src/Support/RenderProfileAssetRenderer.php
  • Font optimization — partial. Font preload is now supported through manifest resource hints, but no font-display: swap enforcement or subsetting exists yet. Critical CSS extracts @font-face rules but does not infer underlying font files automatically.
  • Lazy-loading of below-the-fold content — only partial. AssetSlot::BelowFold exists in the enum but is never assigned (grep confirms it appears only as a fallback comparison). No DOM-level lazy-load (iframes, embeds, images). The WidgetAssetRegistry condition mechanism is the natural home for this but is dead (see §4).
  • Core Web Vitals / RUM reporting — absent. No CLS guard beyond the body{margin:0} reset, no LCP/INP measurement, no field-data loop to prove the optimizer helps. A “before/after” CWV panel would be the headline selling screenshot.
  • Per-page / per-route profile scope — declared but unreachable. OptimizationScope::PageUrl and RenderProfile exist but the live path forces Layout. Page-level critical CSS is the differentiator over layout-level (hero-heavy landing pages differ from article pages sharing a layout).
  • JS defer/async is coarse. JS strategy is a 3-way (Blocking/Idle/Deferred→module-defer). No per-package async, no module/nomodule split, no inline-small-script threshold.

Differentiator vs table-stakes: minify/bundle, font/image optimization, and preload hints are table stakes a buyer assumes are present given the name. The genuine differentiator already half-built is real-browser, layout-scoped critical CSS via Playwright against final rendered output (docs/critical-css.md) — that is rare and good; lead with it and finish the table-stakes around it.

  • Two dead public registries advertised as primary surfaces. LayoutAssetRegistry and WidgetAssetRegistry are bound as singletons (src/Providers/FrontendOptimizerServiceProvider.php:51-52) and headlined in docs/assets-and-render-profiles.md as the top two “Main Surfaces”, with full registration examples. Grep across the whole monorepo (packages/) shows no caller anywhere outside their own definition, the provider binding, and tests — and the live renderer (CapellFrontendAssetManifestRenderer) never consults either registry; it builds the asset set directly from the manifest. WidgetAssetRegistry::resolve() is never invoked even though the frontend FrontendAssetContextData carries widgetResourceUsages that would feed it. This is shipped dead code presented as the headline API. — src/Support/LayoutAssetRegistry.php, src/Support/WidgetAssetRegistry.php
  • Done/Shipped: critical health diagnostics are real. Diagnostics now reports concrete renderer binding, storage, generator, and queue-driver readiness instead of a version-only pass. Evidence: tests/Unit/Health/FrontendOptimizerHealthCheckTest.php. — src/Health/FrontendOptimizerHealthCheck.php
  • @frontendOptimizerAssets Blade directive has no consumer. The directive is registered (...ServiceProvider.php:56) and documented, but no theme/layout Blade in the monorepo calls it — the production path goes through the manifest renderer override instead. It takes a raw $profileHash string and echos RenderProfileAssetsAction::run(...); if a theme author ever passes attacker-influenced input it is a profile-hash lookup only (low risk) but the unused directive is confusing dual-surface debt.
  • Public-output safety: mostly good, one gap. RenderProfileAssetRenderer escapes hrefs/srcs with htmlspecialchars, neutralises </style in inline CSS, and JSON-encodes the idle-loader src with hex flags — anonymous output reveals no model IDs/paths/labels. However the renderer reads $profile->signature['assets'] (a stored array) and emits path values directly; those paths originate from manifest registration so are trusted today, but there is no test proving anonymous/non-admin rendering safety end-to-end through the real head blade — the skill mandates such a test for any rendering/cache change. Test gap, not a confirmed leak. — src/Support/RenderProfileAssetRenderer.php
  • Interaction with html-cache / cacheable:false contradiction. Resolved for generation completion: capell.json now declares FrontendRenderProfile updates as an invalidation source, the provider registers the model with the package-neutral frontend cache invalidation registry, and GenerateCriticalCssAction invalidates after successful CSS generation. This intentionally avoids importing capell-app/html-cache internals. Remaining risk: invalidation is broad because render profiles do not yet record exact cached URL coverage. — capell.json:63-74, src/Actions/GenerateCriticalCssAction.php, src/Actions/InvalidateGeneratedCriticalCssCacheAction.php
  • Done/Shipped: manifest writes moved off the normal render path. The live render() path still creates or updates the render profile record for new signatures, but PrepareRenderProfileAction now writes the JSON manifest only after asynchronous critical-CSS generation is eligible and a generation claim succeeds. Sync-queue public renders and already-queued profiles avoid local disk writes, and PersistRenderProfileAction preserves an existing manifest path when called without a new manifest. Remaining budget risk: the first profile render still performs DB work, so the frontendRenderBudgetMs:20 claim should be treated as a cold-profile target rather than a zero-I/O guarantee. adminQueryBudget:0 is also implausible given the configurator/settings surfaces. — src/Actions/PrepareRenderProfileAction.php, src/Actions/PersistRenderProfileAction.php, src/Actions/StoreRenderProfileManifestAction.php, capell.json:59-62
  • createOrFirst race under concurrent first-render. Two simultaneous requests for a new layout can both miss, both attempt createOrFirst; the unique index on hash saves correctness but one request eats a duplicate-key round trip. Acceptable, and the duplicate request now avoids rewriting the manifest unless it wins the generation claim. — src/Actions/PersistRenderProfileAction.php, src/Actions/StoreRenderProfileManifestAction.php
  • Critical-CSS sanitiser is fragile regex over CSS. sanitizeInlineCriticalCss() strips @property and rewrites margin-trim/container-type rules with brittle escaped-class regexes tied to Tailwind v4 output (src/Support/RenderProfileAssetRenderer.php:185-200). This will silently break or pass-through on a theme that does not emit exactly that markup, and is undocumented as Tailwind-specific. Tech debt + correctness risk on non-Foundation themes.
  • i18n. Settings/configurator labels are translated (capell-frontend-optimizer::settings.*), good. But ExtensionManagementSurfaceData::settings(label: 'capell-frontend-optimizer::settings.critical_css') and the heroicon-o-bolt are fine; no hardcoded user-facing strings spotted. profileLabel() concatenates raw layout/theme keys — internal only, never shown to anonymous users. OK.
  • Done/Shipped: test gap reconciliation. The previously listed gaps are now covered: CaptureCriticalCssPageTypeOptOut has hydrated/no-query and lazy-load listener coverage, FrontendOptimizerHealthCheck has real diagnostics coverage, GenerateCriticalCssJob covers generated short-circuit and WithoutOverlapping, CriticalCssSettings covers persisted/config fallback and viewport parsing, and PublicHeadOutputSafetyTest covers anonymous/non-admin head rendering. Remaining useful depth is command-level coverage for newer console surfaces and broader browser-generation failure modes, not the original listener/job/settings/health gap. — tests/Unit/Listeners/CaptureCriticalCssPageTypeOptOutTest.php, tests/Unit/Actions/GenerateCriticalCssActionTest.php, tests/Unit/Support/CriticalCssSettingsTest.php, tests/Unit/Health/FrontendOptimizerHealthCheckTest.php, tests/Feature/PublicHeadOutputSafetyTest.php

Current copy critique. Manifest summary and composer description are near-identical and weak: “Profile-based CSS and JavaScript delivery for public Capell pages.” “Profile-based delivery” is internal jargon (it describes the mechanism, a hash bucket), names no benefit, and undersells the one genuinely strong feature — real-browser critical CSS. A buyer scanning the marketplace cannot tell this improves page speed / Core Web Vitals. The word “Optimizer” promises minify/images/bundling that the code does not do (§3), creating an expectation gap that will drive refunds/support.

Improved 1-sentence summary:

Faster first paint for public Capell pages: automatic above-the-fold critical CSS extracted from your real rendered pages, plus smart deferring of non-critical CSS and JavaScript.

Improved 3–4 sentence description:

Frontend Optimizer speeds up your public Capell site by inlining the exact above-the-fold CSS each layout needs and deferring everything else, so visitors see styled content sooner and your Core Web Vitals improve. Unlike source-scanning tools, it generates critical CSS from your pages as a real browser renders them — Layout Builder content, theme markup, render hooks, and responsive styles all included — across mobile and desktop viewports. Generation runs on your queue, never in the public response, and falls back safely to standard stylesheet delivery until critical CSS exists. Per-layout render profiles mean equivalent pages reuse one optimized result, and page types you don’t want to optimize can opt out with a single toggle.

Screenshot / media gaps. The required Capell screenshot-runner captures are committed and listed in docs/screenshots.json: profile asset output and critical-CSS output, each with a dark-mode variant. Marketplace promotion still keeps only the extension card because the product-specific captures need recapture before sales use. Remaining media upside: add a “before/after Lighthouse / CWV” comparison (the strongest sales asset) and surface the settings panel screenshot when the admin workflow is buyer-ready.

Pricing / tier / bundle. Positioned premium / foundation bundle — correct: performance is a cross-cutting concern every site wants, so bundling with the foundation drives adoption and it is a natural upgrade-justifier for the premium tier. Keep it foundation-bundled but ensure the image/font gaps (§3) are closed before leaning on “Optimizer” as a premium differentiator, or competitors’ all-in-one perf packages will look more complete.

Cross-sell (deps + Extension Suites).

  • html-cache — natural pair: critical CSS makes the first (cacheable) paint fast; html-cache makes repeat paints instant. Sell as “Speed Suite”. Must fix the generation→cache-invalidation gap (§4) first so the story holds.
  • media-library — the image-optimization seam now accepts hydrated media URLs and renders responsive/AVIF/WebP candidates; bundle “Optimizer + Media Library” to own LCP end-to-end.
  • seo-suite — Core Web Vitals are a ranking factor; co-position “Optimizer + SEO Suite” as “rank faster, load faster”.
  • foundation-theme — already the only theme with critical-eligibility wired (§2.5); make that explicit (“optimized out of the box with Foundation”).

Differentiators / value props / target buyer. Differentiator: real-browser, layout-scoped, queue-driven critical CSS with safe fallback and per-page-type opt-out. Value props: faster LCP/FCP, no theme rewrite, zero risk to public output (always falls back). Target buyer: agency/site owner running content/marketing sites on Capell who cares about Lighthouse scores and SEO but won’t hand-tune asset loading.

Keywords/tags (8–12): critical-css, core-web-vitals, page-speed, lcp, render-blocking, css-optimization, lazy-loading, defer-javascript, above-the-fold, performance, frontend-performance, lighthouse.

ItemBucketEffortImpactSection ref
Implement real health-check probes (it’s critical-severity but a stub)DoneMHigh§2.1, §4
Add anonymous/non-admin public-output safety integration test through real head renderDoneMHigh§4 (test gaps)
Wire critical-CSS generation completion → html-cache invalidation sourceDoneMHigh§4
Recapture optimizer-specific profile asset and critical CSS screenshots before promoting marketplace media; generic Demo/consent-banner captures were demotedFutureSMed§5
Remove or wire dead registries (LayoutAssetRegistry/WidgetAssetRegistry) + fix docs headlineDoneSMed§4
Rewrite marketplace summary + composer description (benefit-led)DoneSHigh§5
Delete/implement dead debug_query_support settingDoneSMed§2.3
Delete or wire orphaned ResolveOptimizationScopeAction; reconcile unreachable scopesDoneSMed§2.2, §3
Validate/relax frontendRenderBudgetMs:20 claim; move manifest disk-write off the hot render pathDoneMHigh§4
Drive critical-eligibility + JS-idle from manifest hints, not hardcoded foundation-theme:*DoneMHigh§2.5, §2.6
Shipped 2026-06-08: add image optimization seam (responsive/lazy/eager/AVIF-WebP candidates) paired with media-library URLsDoneMHigh§3
Add preload/preconnect/fetchpriority + font optimization hintsDoneMHigh§3
Add profile/critical-CSS GC (prune on layout/theme delete + stale signatures)DoneMMed§2.7
Cover listener, job, settings, health in testsDoneMMed§4 (test gaps)
Implement page-level (PageUrl) scope for hero-heavy landing pagesLaterLMed§3
Add CWV/RUM before-after reporting panel (headline sales asset)LaterLHigh§3, §5
Add CSS/JS minify + bundling to match “Optimizer” naming expectationLaterLMed§3, §5
Document Tailwind-v4-specific critical-CSS sanitiser coupling; harden for other themesLaterMMed§4