Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Client Laravel Package

simplestats-io/laravel-client

A lightweight Laravel client for Simple Stats, the privacy-friendly, cookie-free analytics platform. Track pageviews and custom events from your app using a clean API, Blade directives, and middleware, with GDPR-friendly defaults, simple configuration, and no complex setup required.

View on GitHub
Deep Wiki
Context7

Getting Started

This release matters if your site's HTML is served without PHP running — Statamic "full measure" static caching, nginx/Varnish proxy_cache, or Cloudflare "Cache Everything". In those setups the tracking middleware never fires and visits are silently lost. If PHP still runs on every request (including Statamic's "half measure"), you don't need any of this and should skip the whole beacon.

Minimal steps:

  1. Update the package, then check your published config/simplestats-client.php. Existing installs will not have the new block — no key means no route is registered and nothing changes. Either php artisan vendor:publish --tag=simplestats-client-config --force or paste the block in manually:
'beacon' => [
    'enabled' => env('SIMPLESTATS_BEACON_ENABLED', false),
    'path' => env('SIMPLESTATS_BEACON_PATH', 'assets/v'),
],
  1. Pick a path that looks like a static asset but is not a real file. assets/v is a sane default; assets/beacon.gif is not (see Gotchas).
  2. Flip SIMPLESTATS_BEACON_ENABLED=true and confirm the route exists: php artisan route:list | grep assets.
  3. Drop the snippet into your layout — for Statamic, resources/views/layout.antlers.html, just before </body>.

First use case to validate: load a cached page with ?utm_source=test, watch the network tab for the pixel/fetch, and confirm the visit lands in your dashboard with the entry page and utm_* intact. Then load the same page twice from cache and check you get two visits, not one (proves the middleware ran) — and load a page once as a cache miss and check you get one visit, not two (proves deduplication works).

Implementation Patterns

Layout snippet (JS + no-JS fallback). Copy it verbatim; both halves matter:

<script>
    (function () {
        var params = new URLSearchParams(window.location.search);
        params.set('page', window.location.pathname);
        params.set('document_referer', document.referrer);
        fetch('/assets/v?' + params.toString(), { keepalive: true });
    })();
</script>
<noscript>
    <img src="/assets/v" alt="" width="1" height="1" style="position:absolute">
</noscript>

The fetch path covers two things the bare <img> cannot: document.referrer (external referrer — the browser exposes it to JS only) and keepalive, so the request survives the unload of a fast navigation.

Where the data comes from. The embedding page is sent as the Referer of the pixel request. For same-origin requests that header carries the full URL including query string, so page and every utm_* parameter are derived server-side without JS. That's why the beacon lives on the same origin as the pages — don't move it to a separate assets/CDN domain, or you lose the query string (and possibly the referer entirely, depending on Referrer-Policy).

Statamic full measure. Point your static cache at the layout, clear and rebuild the cache so the snippet is actually baked into the HTML. Cached pages keep serving whatever snippet they were built with — a rebuild is mandatory.

Middleware group placement. The route is registered in the first group listed in middleware_groups, so it always carries the tracking middleware — that's deliberate, since the endpoint must pass through bot filtering, visitor hashing and dedupe. Keep that group lean but functional: don't put the beacon group behind auth, and don't assume a web group with session/CSRF is required (it's a GET, but session middleware on a per-page-view pixel is pure overhead).

Deduplication is automatic. A cache miss served by the app plus the beacon for the same page count as one visit. Don't try to suppress the beacon on cache misses — you'd need to know the cache state at render time, and the package already handles it.

Prefer half measure when you can. If a caching strategy still lets PHP run, keep tracking fully server-side and leave the beacon off. It's one less moving part, works with JS disabled, and has no Referrer-Policy caveats.

Gotchas and Tips

Subclassing the middleware is now a breaking change. CheckTracking::getPage() and CheckTracking::doTracking() gained an optional parameter. If you override either, add it to your signature or PHP throws a fatal "incompatible declaration" — this bites at boot, not at request time, so a container that won't start is the symptom.

Path naming matters twice. Paths containing track, stats, analytics, beacon, collect or pixel are on common ad-blocker lists — a blocked request means zero visits and no error anywhere. And a path with a file extension (v.gif) may be cached by your CDN before it ever reaches the app, which also silently zeroes out counting. Use an extensionless path like assets/v and, if you're on a CDN, add an explicit cache bypass for it.

Referrer-Policy can break the whole scheme. no-referrer strips the Referer on the pixel, so the page URL and utm_* are gone. Same-origin requests need at least a policy that preserves the full URL for same-origin — verify with the browser's network tab before trusting the numbers. Also check Referrer-Policy/meta tags in the layout itself, not just the server config.

Foreign referers are dropped by design. Requests whose referer points at another domain are discarded so a scraped or mirrored copy of your HTML can't inflate your numbers. The flip side: staging domains, aliased domains, or a CDN that rewrites the referer will drop traffic. Test on the production hostname.

navigator.sendBeacon will not work. It sends a POST and only GET requests are tracked. Don't "improve" the snippet.

The <noscript> fallback is not redundant. It's the no-JS path, and it's what the whole Referer-based derivation was designed around. Keep the inline styles — display:none on a tracking pixel is frequently blocked.

Malformed input is now neutralized, not rejected visibly. Tracking codes are restricted to scalars, so ?utm_source[]=x no longer produces a payload the API rejects — but it also means array-style params are silently dropped. If you're generating beacon URLs programmatically, pass strings.

Don't put the beacon path behind a catch-all route (SPA fallback, Statamic 404 route). If your catch-all wins, the request is served as a normal page and never counted. Check route:list ordering after adding any catch-all and, on nginx, make sure the beacon path isn't swallowed by try_files.

Debugging checklist when counts are flat: route registered at all (missing config key ⇒ no route)? enabled true? request blocked by an ad blocker (try an incognito profile with no extensions)? CDN served the pixel from cache? Referer present on the request? referer host matching your app host? middleware group actually applying CheckTracking? Work down that list in order — every one of these fails silently.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast