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 Spa Analytics Laravel Package

fojlerabbirabib/laravel-spa-analytics

A Laravel package for adding SPA analytics to single-page applications, helping you track page views and user activity without full page reloads. Lightweight and easy to integrate into Laravel projects.

View on GitHub
Deep Wiki
Context7

Getting Started

This package adds SPA-aware page-view and event analytics to a Laravel app: it ships migrations, a config file, a browser script, and a Blade directive that renders a versioned script URL. Day-to-day, you mostly interact with three things: config/spa-analytics.php, the @spaAnalytics directive in your layout, and the migrations/artisan commands.

Minimal setup:

  1. Install and publish: config, migrations, and assets (--tag=spa-analytics-assets). If you customize the package views, publish them too and keep them in sync manually.
  2. Add @spaAnalytics to your root Blade layout (typically <head> or just before </body>). Do not hardcode the script path; the directive emits src="{{ $scriptUrl }}", which now includes a ?v= hash.
  3. Run php artisan migrate. On a fresh install the folded create_* migrations produce the final schema; the new update_analytics_tables_rev_2 is a no-op there because every change is guarded.
  4. Check php artisan about after upgrading or in CI — it reports how many legacy update_analytics_* migration files remain in your app.

First use case to validate: load an Inertia page, click a <Link>, and confirm one page view is recorded (not zero, not two). The v0.8.0 Inertia prefetch fix is exactly this path, so it is the best smoke test. Where to look first when something is off: config/spa-analytics.php (especially database.connection), the published client-script.blade.php, and the ClientScript service (container-bindable if you need to override URL generation).

Upgrading from any prior version is a single hop to 0.8.0:

php artisan vendor:publish --tag=spa-analytics-migrations --force
php artisan vendor:publish --tag=spa-analytics-assets --force
php artisan migrate

Then, only if you had published views, copy src="{{ $scriptUrl }}" into your client-script.blade.php.

Implementation Patterns

Layout integration and asset delivery. Render the script through the directive so the ?v= hash is applied on every render. Treat client.js as an immutable asset: deploy it before the HTML that references it, and configure your web server or CDN to key its cache on the query string. That combination lets you set a long Cache-Control and still guarantee visitors pick up a re-published script.

Inertia and prefetch. The browser script now reports every single-page transition, Inertia included, and the server ignores a client page view for a path it recorded within the last five seconds. This makes normal <Link prefetch> visits count once: the prefetch request is ignored server-side, the click sends no request, and the client-side transition supplies the page view. Back and forward navigation on Inertia pages is counted too, which it was not before.

Isolating analytics onto its own database connection. Set SPA_ANALYTICS_DB_CONNECTION (or spa-analytics.database.connection) when you want analytics tables off your primary database — useful for read/write splitting, separate retention policies, or noisy-neighbor isolation. The package follows the connection through models, raw queries, the three transactions (event store, re-linking, rollups), the MySQL/MariaDB binary check, and the migrations. Laravel's own migrations table stays on the default connection, so your migration bookkeeping is unaffected.

Correct rollout order matters:

  1. Configure the connection key first.
  2. Re-publish migrations with --force so the connection name is baked into the migration files.
  3. Then run migrate for the first time on that environment.

Do not set the key on an environment that already has data in the default connection — it moves no data, it only redirects future reads and writes. For migrate:fresh and multi-connection transaction caveats, follow the README rather than improvising.

Migration hygiene in CI and deploys. The package deletes the seven legacy update_analytics_* files automatically once update_analytics_tables_rev_2 is published — right before migrate, migrate:fresh, or migrate:refresh collects pending migrations, and after publishing the package tags. It only matches exact file names, only inside database/migrations and paths outside vendor/, and never touches the migrations table. Useful patterns:

  • Run php artisan spa-analytics:clean-migrations as an explicit deploy step if you prefer determinism over the implicit hook.
  • Assert in CI that php artisan about reports zero leftover legacy migration files.
  • Note that --pretend does not trigger the cleanup; do not rely on a dry run to tidy files.
  • If a developer restores an old migration from git, it is harmless: the migrations table marks it as already run.

Upgrades from any version. Because the folded create_* migrations hold final columns and the single update_analytics_tables_rev_2 is fully guarded, upgrades from 0.1.0 onward converge to the same schema. This simplifies long-lived forks and environments that skipped releases.

Extension points. ClientScript is bindable in the container, so you can swap it if you need custom URL/versioning behavior (for example, resolving the hash from a manifest instead of the published file). Custom published views are the other extension point — but they are now a maintenance liability, since you must manually port src="{{ $scriptUrl }}".

Gotchas and Tips

  • --force overwrites. Publishing migrations and assets with --force clobbers local edits. Diff published views before upgrading, and copy src="{{ $scriptUrl }}" into your client-script.blade.php rather than re-publishing over it.
  • Connection changes are not migrations of data. Setting spa-analytics.database.connection after the tables exist leaves your historical rows behind. Decide before the first migrate, or write your own data copy.
  • Re-publish migrations when enabling the connection. A config-only change is not enough: the connection is named inside the migration files. A published config file without the key is fine, since the default (null) fills in.
  • No hash without a readable published file. The ?v= value comes from hashing the published script on every render. If the file is missing or unreadable, the URL has no version and aggressive caching becomes unsafe again. Nothing is hashed while tracking is disabled, so the URL is stable in that state.
  • Deploy order for the cache-busting to work. Script first, HTML second. If your CDN ignores query strings, the ?v= does nothing and a stale client.js can persist in browsers — this is the exact bug v0.8.0 fixes, so verify your cache key configuration.
  • Inertia dedupe window is five seconds. A response slower than five seconds can count one visit twice. If you have slow server responses or long-lived prefetched pages, expect occasional double counts and account for it in reporting.
  • replaceState visits served from prefetch cache are still not counted. Client-side routing that only calls replaceState and hits the cache will not produce a page view. Rare in practice, but worth knowing before you trust the numbers for those flows.
  • The migrations table never moves. Even with a custom analytics connection, Laravel's migration bookkeeping stays on the default connection. Do not point your whole database/migrations config at the analytics connection to "match."
  • Cleanup is intentionally narrow. Only exact legacy filenames are deleted, and only outside vendor/. If you renamed or moved those files, they will not be removed — use spa-analytics:clean-migrations output and about counts to confirm, then delete manually.
  • Debugging missing page views. Check in this order: is @spaAnalytics rendered with a versioned URL, did the new client.js actually reach the browser, is the server dropping the view via the five-second dedupe, and is the request hitting the configured analytics connection (queued writes and transactions included).
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.
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc
animalyoung/laravel-influxdb
onix-solutions/sms-telera
lonely-lights/prosetta
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log