voidgraphics/laravel-pageview-counter
A simple Laravel package for tracking and counting page views. Record visits, retrieve view totals for pages or models, and add lightweight pageview analytics to your app without relying on external services.
v0.2.0 is a compatibility release: it adds Laravel 12 & 13 support with no new features or documented breaking changes. If you are already on v0.1.x and running Laravel 11 or older, there is no functional reason to upgrade — but there is also no reason to avoid it, and the bump signals the maintainer is keeping the package current, which matters for a 0.x dependency sitting in your request path. Treat v0.2.0 as your baseline for new work.
Install and wire it up:
composer require voidgraphics/laravel-pageview-counter:^0.2
php artisan vendor:publish --provider="VoidGraphics\PageviewCounter\PageviewCounterServiceProvider"
php artisan migrate
Check config/pageview-counter.php immediately after publishing — this is the file that determines whether the package is useful or noisy for your app. Look first at three things: the storage table/model it writes to, whether writes are synchronous or queued, and whether there is any built-in visitor-uniqueness or bot-filtering option. Those three settings account for most of the surprises people hit on day one.
Your first use case should be the smallest one: attach the package's middleware to a single route group (a public marketing page or blog show route) rather than globally, hit the page a few times, and confirm rows land in the database. Once you can see the raw counts, decide whether you need per-model counts (a Page or Post record) or per-URL counts — the two shapes drive very different schema and query decisions later.
Middleware on selected route groups. The lowest-friction integration is scoping the middleware to public, read-only routes. Global registration will count /login, /api/*, health checks, and Livewire polling traffic, which will swamp your real numbers within a day.
Route::middleware(['web', 'pageview'])->group(function () {
Route::get('/blog/{post}', [PostController::class, 'show']);
});
Per-model counting via a trait. If the package exposes a trait for Eloquent models, prefer it over URL-keyed counting. It survives slug changes, gives you foreign keys for reporting joins, and lets you eager-load counts rather than running aggregate queries per page render. Add the count column or relation to your index/show queries so listing pages don't N+1.
Explicit recording for custom logic. Middleware is convenient but dumb — it can't know that a preview request, an admin session, or a ?draft=1 query string shouldn't count. For anything with real business meaning, record explicitly in the controller or a dedicated action class after your own guards run. This is also where you attach user IDs, tenant IDs, or referrer metadata the middleware won't capture.
Separate the write path from the read path. Counters in a request cycle become a write hotspot the moment a page gets traffic. Two patterns that hold up:
ShouldQueue listener/job) if the package supports it, accepting a few seconds of lag.increment() on the aggregate table.Either way, the counter table should be append-only with a nightly or hourly rollup, and your dashboard should read the rollup — not COUNT(*) over millions of raw rows.
Test with a fake or a transaction. Counter packages almost always make assertions awkward because they write outside your test's expectations. Wrap feature tests in RefreshDatabase and assert on the count table directly, or disable the middleware in your base test case and test the counting action in isolation. Without this, every unrelated feature test starts failing on row-count assertions.
0.x. There is no semver promise on the public API, and the jump from v0.1.0 to v0.2.0 was a Laravel support bump — the next minor could rename config keys or change the middleware alias. Use ^0.2 and read the changelog before each bump.Sec-Purpose: prefetch / Purpose: prefetch headers before you trust any number.robots.txt-respecting heuristics) or accept it and only report on converted sessions.php artisan config:cache will silently serve stale settings in staging. When counts stop appearing, this is the first thing to clear.created_at and group by your subject column. Add a composite index on (subject_type, subject_id, created_at) in the same migration that creates the table, not after the table hits a million rows.increment() or a raw atomic update; under concurrent requests the read-modify-write pattern loses views and nobody notices until the numbers are compared against server logs.How can I help you explore Laravel packages today?