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 Pretty Pagination Laravel Package

ctsoft/laravel-pretty-pagination

A Laravel package that provides clean, customizable pagination views for a more polished UI. Drop-in Blade templates make it easy to replace default Laravel pagination links with attractive, responsive styling and minimal setup.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish

composer require ctsoft/laravel-pretty-pagination
php artisan vendor:publish --tag=pretty-pagination-config

The service provider is auto-discovered. Open config/pretty-pagination.php first — that is where the URL segment prefix (typically page), the middleware toggle, and any per-route exclusions live. Read this file before touching anything else; the package is thin and almost all behaviour is driven from here.

Wire it in

Register the package middleware on the web group (or only on the routes you want pretty URLs for) in bootstrap/app.php / app/Http/Kernel.php:

->withMiddleware(function (Middleware $middleware) {
    $middleware->web(append: [
        \Ctsoft\PrettyPagination\Middleware\PrettyPagination::class,
    ]);
})

First use case

A blog index that paginates as /blog/page/2 instead of /blog?page=2:

// routes/web.php
Route::get('/blog/{page?}', [PostController::class, 'index'])
    ->where('page', '[0-9]+')
    ->name('blog.index');

// PostController
public function index()
{
    return view('blog.index', [
        'posts' => Post::paginate(15),
    ]);
}

In the Blade view keep using the stock API:

{{ $posts->links() }}

If the URLs render as /blog/page/2, the middleware and config are working. That single check is your smoke test for the whole package.

Implementation Patterns

Middleware-scoped rollout. Don't enable it globally on day one. Apply it to the route groups that need SEO-friendly pagination (blog, category, product listings) and leave admin/API routes on the default ?page= behaviour. This limits the blast radius if a route has a conflicting {slug} segment.

Parameterised listing pages. The common real-world shape is filters + pagination. Keep filters in the query string and let the package own only the page segment:

// /products/page/3?sort=price&brand=acme
$products = Product::query()
    ->when(request('brand'), fn ($q, $b) => $q->where('brand', $b))
    ->when(request('sort'), fn ($q, $s) => $q->orderBy($s))
    ->paginate(24)
    ->withQueryString();

withQueryString() is the piece that keeps filters attached to the pretty URL. Without it, page 2 silently drops sort and brand.

Canonicalise page 1. Treat /blog/page/1 and /blog as the same resource and 301 the former to the latter. Duplicate content from page-1 variants is the most common SEO regression when adopting this package:

if ((int) request()->route('page') === 1) {
    return redirect()->route('blog.index'), 301);
}

Preserve inbound ?page= links. Old bookmarks and external links will still hit /blog?page=2. A short redirect shim (either in the controller or a small middleware placed before the package middleware) converts ?page=N into the pretty form so you don't lose traffic or split analytics.

Pagination in tests. Assert on the pretty URL directly; this catches route-ordering mistakes early:

$this->get('/blog/page/2')
    ->assertOk()
    ->assertSee('page/3');

Pair with a route model or where constraint. Always constrain the {page} parameter (->where('page', '[0-9]+')) so a slug like /blog/page/about doesn't get swallowed by the pagination route.

Gotchas and Tips

Route ordering is the #1 failure mode. /blog/{page?} will happily match /blog/my-post-slug if registered before /blog/{slug}. Register the paginated listing route after any concrete or slug routes, or constrain {page} to digits. If pagination links start 404-ing, run php artisan route:list --path=blog and check which route wins.

Cache keys must include the page segment. Response caching keyed only on the path minus the page number will serve page 1's HTML for page 3. Include request()->route('page') in the cache key, or exclude paginated pages from full-page caching entirely.

Middleware order matters. The pretty-pagination middleware needs to resolve the page number before anything that reads Paginator::resolveCurrentPage() (e.g. a query-logging or cache middleware). If page numbers come out as 1 everywhere, move the package middleware earlier in the stack.

links() vs. hand-built URLs. If you generate pagination URLs manually (route('blog.index', ['page' => 2])), you will get /blog?page=2 — the route parameter is a path segment only if the route declares {page}. Either declare the segment or route through the paginator's own url() helper.

Last release was September 2023. Verify against your Laravel version before committing. On Laravel 11+ the package's middleware registration assumptions may need adjusting; run the test suite on a branch and check composer why-not if install fails. There are zero dependents, so treat it as low-bus-factor code you may need to fork.

Extension points. The config file is the intended seam — change the URL prefix (/seite/2 for localised sites), toggle the middleware, or exclude routes. If you need different prefixes per section (e.g. /blog/page/2 but /shop/p/4), that usually requires a fork or a thin wrapper middleware rather than config alone.

Debugging checklist when links look wrong: (1) is the middleware actually on this route group — check route:list -v; (2) does the route declare {page?}; (3) is withQueryString() present; (4) is APP_URL correct, since paginator URLs are absolute; (5) clear php artisan route:clear && php artisan config:clear after config edits.

Sitemaps. Pretty URLs are a genuine win here — list /blog/page/N up to the last page in your sitemap instead of query-string variants that crawlers treat inconsistently. Cap it (e.g. first 20 pages) so the sitemap stays useful.

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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource