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 Pageview Counter Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

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.

Implementation Patterns

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:

  • Queue the write (ShouldQueue listener/job) if the package supports it, accepting a few seconds of lag.
  • Buffer in cache and flush on a schedule for high-traffic pages, then reconcile with atomic 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.

Gotchas and Tips

  • Pin the constraint. You are on 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.
  • Laravel 13 support is forward-looking. Confirm your CI matrix actually runs the version you deploy on; compatibility claims for unreleased or newly released framework majors are worth verifying with one smoke test rather than trusting the release note alone.
  • Prefetch and prerender inflate counts. Chrome's speculation rules and Safari's prefetching will fire requests your middleware happily counts. Exclude them with the Sec-Purpose: prefetch / Purpose: prefetch headers before you trust any number.
  • Bots are the default. Unless the package ships filtering, assume a meaningful share of your rows are crawlers. Either filter at the middleware layer (user-agent + robots.txt-respecting heuristics) or accept it and only report on converted sessions.
  • Uniqueness is a design decision, not a checkbox. If there's a "unique views" concept, it's session- or IP-based with a TTL. Note that IP storage is personal data under GDPR — hash it, set a retention window, and document it. Also check the TTL: too long and returning visitors vanish, too short and refreshes count twice.
  • Config cache after every config edit. php artisan config:cache will silently serve stale settings in staging. When counts stop appearing, this is the first thing to clear.
  • Add indexes before you have data. The read queries will filter on 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.
  • Race conditions on increments. Never read-increment-write in PHP. Use 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.
  • Extension points worth knowing. The middleware is the natural place to subclass for custom filtering, and the model is the natural place to override for custom table names or connections (multi-tenant apps will need a per-tenant connection). If the package fires events on record, hook those for downstream analytics instead of adding more writes to the request cycle.
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.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package