In this comprehensive guide, we'll explore how to implement infinite scrolling in a Laravel application using Inertia.js v2.0 and Vue 3. We'll cover both the frontend and backend implementation, with special attention to handling full page refreshes and maintaining scroll position.
Table of Contents
- Understanding the Components
- Frontend Implementation
- Backend Implementation
- Real-World Example: Blog Posts with Categories
- Best Practices and Considerations
Understanding the Components
The infinite scrolling implementation relies on three main components:
- Inertia.js v2.0's WhenVisible Component: This component handles the intersection observer logic to detect when we need to load more content.
- Laravel's Pagination: Handles the server-side pagination logic.
- Vue 3's Composition API: Manages our frontend state and reactivity.
Frontend Implementation
Let's start with a Vue component that implements infinite scrolling for a blog posts listing:
<script setup>
import { computed } from 'vue'
import { usePage, WhenVisible } from '@inertiajs/vue3'
import LoadingSpinner from '@/components/LoadingSpinner.vue'
import BlogPostCard from '@/components/BlogPostCard.vue'
const page = usePage()
const hasFeaturePost = computed(() => !!page.props.featuredPost)
const categoryName = computed(() => page.props.category?.name)
</script>
<template>
<div class="bg-gray-50">
<!-- Featured Post Section -->
<div
v-if="hasFeaturePost"
class="container"
>
<div class="py-8 text-center">
<h2 class="mb-4 text-3xl font-bold">
Featured Post: {{ page.props.featuredPost.title }}
</h2>
</div>
</div>
<!-- Posts Grid -->
<div class="max-w-7xl px-4 py-8 mx-auto sm:px-6 lg:px-8">
<h1 class="text-2xl font-bold mb-6">
{{ categoryName ? `Posts in ${categoryName}` : 'All Posts' }}
</h1>
<div class="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
<div
v-for="post in page.props.posts"
:key="post.id"
class="relative flex flex-col"
>
<blog-post-card :post="post" />
</div>
<!-- Infinite Scroll Handler -->
<WhenVisible
:always="page.props.postsPagination?.current_page < page.props.postsPagination?.last_page"
:params="{
data: {
page: page.props.postsPagination.current_page + 1,
},
only: ['posts', 'postsPagination'],
}"
>
<div
v-if="page.props.postsPagination?.current_page >= page.props.postsPagination?.last_page"
class="text-center py-6 text-gray-600 col-span-1 md:col-span-2 lg:col-span-3"
>
You've reached the end!
</div>
<div
v-else
class="col-span-1 md:col-span-2 lg:col-span-3"
>
<loading-spinner />
</div>
</WhenVisible>
</div>
</div>
</div>
</template>
Key Frontend Features
WhenVisible Component: This component from Inertia.js v2.0 automatically triggers a request when the element becomes visible in the viewport.
Pagination Parameters:
:params="{
data: {
page: page.props.postsPagination.current_page + 1,
},
only: ['posts', 'postsPagination'],
}"
-
data
: Specifies the next page to load -
only
: Optimizes the request by only fetching required data
- Loading States: The component handles both loading and end-of-content states elegantly.
Backend Implementation
Here's the Laravel controller implementation that handles both regular pagination and full-page load scenarios:
<?php
namespace App\Http\Controllers;
use App\Models\Post;
use App\Models\Category;
use Illuminate\Pagination\LengthAwarePaginator;
use Inertia\Inertia;
class BlogController extends Controller
{
public function index(?Category $category = null)
{
return Inertia::render('Blog/Index', [
'category' => $category,
'featuredPost' => $this->getFeaturedPost(),
'posts' => $this->getPaginatedPosts($category),
'postsPagination' => $this->getPaginatedPosts($category)?->toArray(),
]);
}
protected function getPaginatedPosts(?Category $category): ?LengthAwarePaginator
{
$currentPage = request()->input('page', 1);
$perPage = request()->input('per_page', 12);
$query = Post::query()
->with(['author', 'category'])
->published();
if ($category) {
$query->where('category_id', $category->id);
}
// Apply any additional filters
if (request()->has('sort')) {
$query->orderBy(request()->input('sort'), request()->input('direction', 'desc'));
} else {
$query->latest();
}
// Handle full page load vs. infinite scroll request
if (!request()->header('X-Inertia')) {
// Full page load - fetch all pages up to current
$allResults = collect();
for ($page = 1; $page <= $currentPage; $page++) {
$pageResults = $query->paginate($perPage, ['*'], 'page', $page);
$allResults = $allResults->concat($pageResults->items());
}
return new LengthAwarePaginator(
$allResults,
Post::query()
->published()
->when($category, fn($q) => $q->where('category_id', $category->id))
->count(),
$perPage,
$currentPage
);
}
return $query->paginate($perPage);
}
protected function getFeaturedPost()
{
return Post::query()
->with(['author', 'category'])
->published()
->featured()
->latest()
->first();
}
}
Key Backend Features
- Pagination Handling:
if (!request()->header('X-Inertia')) {
// Full page load logic
} else {
// Regular pagination for infinite scroll
}
- Full Page Load: When a user refreshes or directly visits a page, we fetch all previous pages to maintain the correct scroll position:
for ($page = 1; $page <= $currentPage; $page++) {
$pageResults = $query->paginate($perPage, ['*'], 'page', $page);
$allResults = $allResults->concat($pageResults->items());
}
- Efficient Querying: The implementation includes relationship eager loading and scoped queries:
$query = Post::query()
->with(['author', 'category'])
->published();
Conclusion
Implementing infinite scrolling with Laravel and Inertia.js v2.0 provides a smooth user experience while maintaining good performance and SEO practices. The combination of Vue 3's Composition API and Inertia.js's WhenVisible component makes it easy to implement and maintain.
Remember to:
- Test the implementation thoroughly, especially for edge cases
- Monitor performance metrics
- Consider implementing a fallback for users with JavaScript disabled
- Keep accessibility in mind when implementing infinite scroll
This implementation can be adapted for various use cases beyond blog posts, such as product listings, image galleries, or any other content that benefits from infinite scrolling.
Top comments (0)