Install
composer require matheusmarnt/ghostwirePublish the config (optional — sensible defaults ship without it):
php artisan vendor:publish --tag=ghostwire-configPublish the compiled JS/CSS assets — without this step the browser gets a 404 for
ghostwire.js/ghostwire.css and wire:ghost never fires:
php artisan vendor:publish --tag=ghostwire-assetsIn a local environment, Ghostwire publishes these assets automatically at boot if
they’re missing, so this manual step mainly matters for staging/production.
Add both directives to your main layout, once — usually right next to
@livewireStyles/@livewireScripts:
{{-- in <head> --}}@ghostwireStyles
{{-- right before </body> --}}@ghostwireScriptsphp artisan ghost:install runs the publish step above and prints this same reminder,
in one command. Run php artisan ghost:install --force after a package upgrade to
republish updated assets — without --force, existing published files are left alone.
Add the directive to any element inside a Livewire component:
<div wire:ghost> <h2>{{ $title }}</h2> <p>{{ $body }}</p></div>Trigger any Livewire update on that component (an action call, wire:model.live, etc.) — Ghostwire synthesizes a matching skeleton from the live DOM automatically, no further markup required. First visible effect: under 60 seconds from composer require.
Don’t want the loading strategy applied everywhere? config/ghostwire.php’s strategy key defaults to 'opt-in': a component is instrumented when #[Ghost] is declared on it, on an ancestor, on a trait it uses, or on one of its action methods — or when any element in its view carries wire:ghost — or when strategy is set to 'global'. Flip strategy to 'global' to apply the loading strategy to every Livewire component, opt-out per component with wire:ghost.off or #[Ghost(mode: 'off')].
Want every #[Ghost]-opted-in component to emit panel bones by default, without declaring #[Ghost(panels: true)] on each one? Set GHOSTWIRE_PANELS=true (or config/ghostwire.php’s panels key) — it defaults to false. A class-level #[Ghost(panels: ...)] still overrides this app-wide default per component, same precedence as mode/delay/hold. See #[Ghost] attribute for what panels does.
Set GHOSTWIRE_ENABLED=false (it defaults to true) to disable Ghostwire entirely — no hook registration, no data-ghost payload, no lazy-placeholder tagging, and @ghostwireStyles/@ghostwireScripts both emit nothing. If config/ghostwire.php has been cached (php artisan config:cache), changing this env var only takes effect after php artisan config:clear (or a fresh cache).
Which mechanism, for which moment
Section titled “Which mechanism, for which moment”Ghostwire ships three distinct loading mechanisms that are easy to conflate — each covers a different moment in a page’s lifecycle:
| Mechanism | Covers |
|---|---|
wire:ghost / #[Ghost] | A Livewire request on an already-loaded page (an action call, wire:model.live, polling) |
#[Lazy] + #[Ghost(lazy: true)] + learning | The initial page load / a browser refresh, in development — a locally learned skeleton paints into the lazy placeholder |
ghost:export + placeholder() | The initial page load / a browser refresh, in production — a static, server-side Blade placeholder, no JS or localStorage involved |
None of the three substitute for another: wire:ghost alone does nothing on a full
page load (there is no Livewire request or DOM morph to intercept), and learning never
runs in production regardless of config. See /docs/learning
for the full walkthrough of the second and third mechanisms.