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.
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:
--tag=spa-analytics-assets). If you customize the package views, publish them too and keep them in sync manually.@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.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.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.
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:
--force so the connection name is baked into the migration files.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:
php artisan spa-analytics:clean-migrations as an explicit deploy step if you prefer determinism over the implicit hook.php artisan about reports zero leftover legacy migration files.--pretend does not trigger the cleanup; do not rely on a dry run to tidy files.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 }}".
--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.spa-analytics.database.connection after the tables exist leaves your historical rows behind. Decide before the first migrate, or write your own data copy.null) fills in.?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.?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.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.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."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.@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).How can I help you explore Laravel packages today?