Clycyo
Frameworks5 min read

Laravel Analytics: Blade, Livewire, and Server-Side Events

Privacy-first analytics for Laravel apps: layout installation, Livewire navigation, and server-side revenue events from your billing code.

Laravel apps span the whole rendering spectrum — classic Blade pages, Livewire's HTML-over-the-wire, Inertia SPAs — and the analytics setup differs slightly for each. The good news: it is one snippet in one layout file for all three, plus a server-side pattern that fits Laravel's webhook-and-job culture perfectly.

The layout snippet

{{-- resources/views/layouts/app.blade.php --}}
<head>
    <script
        defer
        src="https://clycyo.com/tracker.js"
        data-tracking-id="{{ config('services.clycyo.tracking_id') }}"
    ></script>
</head>

Config-driven so staging and production use different sites. Per rendering mode:

  • Blade (full page loads): every navigation is a real load — pageviews and timing captured with zero extra work.
  • Livewire: component updates do not change the URL and correctly are not pageviews; wire:navigate (SPA-mode) navigations go through the History API and are tracked automatically.
  • Inertia: client-side visits are History API navigations — tracked automatically, same as any SPA.

Client events

<button onclick="window.webanalytics?.track('plan_selected', { plan: 'pro' })">
    Choose Pro
</button>

Identity at login

{{-- In the authenticated layout, once per session --}}
@auth
<script>
  window.addEventListener('load', () => {
    window.webanalytics?.identify(@json(auth()->user()->email));
  });
</script>
@endauth

Persist the visitor ID onto the user (a tiny fetch to a profile endpoint reading getVisitorId()) — it becomes the join key for everything server-side.

Server-side events, the Laravel way

// e.g. in your Cashier/Stripe webhook listener
Http::post('https://clycyo.com/api/collect', [
    'tracking_id' => config('services.clycyo.tracking_id'),
    'type' => 'event',
    'visitor_id' => $user->clycyo_visitor_id,
    'event_name' => 'subscription_paid',
    'event_properties' => [
        'revenue' => $invoice->amount_paid / 100,
        'currency' => strtoupper($invoice->currency),
        'order_id' => $invoice->id,
    ],
]);

Fire it from a queued listener and revenue lands on the visitor's journey, joined to first-touch UTM — the full attribution model with Cashier doing the heavy lifting.

What this replaces

For most Laravel products this setup retires both the GA tag and the cookie banner it required, while adding what GA never had: per-visit load times, JS errors, and funnel events on one record. Start free (10k events/month) and keep the old tag running in parallel for two weeks if you want the receipts.