Master Laravel's simplePaginate for Huge Blog Listings
Facing slow blog listings? Laravel’s simplePaginate can help. This guide walks through its use, indexing needs, custom views, and performance checks.
24 Aug 2025, 00:37 UTC

Concrete Problem: Sluggish Post Listings on a Growing Blog
When a blog starts to amass thousands of posts, the default pagination that Laravel ships with can become a bottleneck. The traditional paginate() method renders a full set of page numbers and performs a COUNT(*) query on every request. On a table with 200,000 rows and no proper indexes, the page load time can jump from 200 ms to several seconds. Developers often wonder: is there a lighter way to show a paged list without breaking the user experience?
Thesis: simplePaginate Is the Right Tool for Large Data Sets
Laravel’s simplePaginate() is designed exactly for this scenario. It generates a paginator that only shows “First” and “Last” links, avoiding the expensive rendering of an entire page range. Under the hood it uses LIMIT and OFFSET clauses, so the database can jump straight to the requested slice of data. The trade‑off is that it still runs a COUNT(*) query to determine the total number of pages, but that cost is usually far lower than rendering all page numbers.
Understanding the Internals
When you call Post::simplePaginate(10), Laravel does the following:
- Builds a query that selects the requested columns.
- Appends
LIMIT 10 OFFSET 0(or 10, 20, …) based on the current page. - Executes a separate
SELECT COUNT(*)to compute the total page count. - Returns a
LengthAwarePaginatorthat renders only the “First” and “Last” links.
<nav>
<a href="?page=1">First</a>
<a href="?page=last">Last</a>
</nav>
Because the paginator does not expose the current page number in the URL (it still uses ?page=2), any client‑side JavaScript that relied on the old paginate() page links must be reviewed.
Practical Example: Listing Posts with a Tailwind‑Styled Paginator
Below is a minimal, reproducible example that demonstrates how to switch from paginate() to simplePaginate() and customize the view to match a Tailwind UI component.
// routes/web.php
Route::get('/blog', function () {
// 10 posts per page
$posts = App\Models\Post::orderBy('created_at', 'desc')
->simplePaginate(10);
return view('blog.index', compact('posts'));
});
// resources/views/blog/index.blade.php
@foreach ($posts as $post)
{{ $post->title }}
{{ $post->created_at->format('M d, Y') }}
@endforeach
{{-- Custom paginator view (resources/views/vendor/pagination/tailwind.blade.php) --}}
@if ($posts->onFirstPage())
First
@else
First
@endif
@if ($posts->hasMorePages())
Last
@else
Last
@endif
To use the custom view, publish the pagination templates with:
php artisan vendor:publish --tag=laravel-pagination
Then rename the default bootstrap.blade.php to tailwind.blade.php and adjust the markup as shown.
Performance Verification: What to Check
After switching to simplePaginate(), it’s essential to verify that the database is using LIMIT/OFFSET efficiently. Open php artisan tinker and run:
$posts = App\Models\Post::simplePaginate(10);
$posts->toHtml(); // Inspect the rendered navigation
Next, compare the generated SQL with EXPLAIN:
$query = App\Models\Post::orderBy('created_at', 'desc')->toSql();
DB::statement('EXPLAIN ' . $query, [/* bind values */]);
The EXPLAIN output should show that the query uses an index on created_at (or the column used in ORDER BY) and that no full table scan occurs. If you see a Using filesort or Using temporary hint, consider adding a covering index.
Trade‑Offs & Limitations
- COUNT Query Still Exists: simplePaginate still runs a
SELECT COUNT(*)to compute the total pages. On very large tables this can be expensive if the column used inWHEREorORDER BYisn’t indexed. - Limited Navigation: Only “First” and “Last” links are rendered. If users expect page numbers, you’ll need to implement a custom navigation component.
- URL Structure: The paginator still uses the
?page=2query string. If your front‑end relies on hash‑based routing or other URL patterns, you may need to adjust JavaScript accordingly. - Offset Performance: Even with indexes, large
OFFSETvalues can cause performance degradation because the database still has to skip rows. For very deep pagination consider keyset pagination instead.
Actionable Checklist
- Replace
paginate()withsimplePaginate(10)in the controller. - Ensure the query has an index on the column used in
ORDER BY(e.g.,created_at). - Publish and customize the paginator view to match your CSS framework.
- Run
php artisan tinkerand verify the rendered navigation and the generated SQL. - Use
EXPLAINto confirm index usage and absence of full table scans. - Test deep pages (e.g., page 500) to observe any offset latency; if needed, switch to keyset pagination.
- Update any front‑end scripts that depend on page numbers in the URL.
By following this checklist, you’ll keep your blog fast, even as the number of posts grows into the millions.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.