Simply copy and paste the following command line in your terminal to create your first Strapi project.
npx create-strapi-app
my-project
Pagination and infinite scrolling significantly improve the user experience of web applications by allowing developers to load large datasets.
Astro.js and Strapi are two powerful tools that developers often use in tandem to create dynamic and efficient web applications.
In this article, you will learn how to enable pagination and infinite scrolling in your Astro.js application using Strapi 5.
Astro.js is a JavaScript framework for building modern, fast, scalable web applications. It provides developers with a clean and organized structure for their code, making it easier to manage and maintain complex projects. Astro.js offers various features and utilities, including routing, state management, and component-based architecture, making it a popular choice among developers for front-end development.
Strapi, on the other hand, is an open-source headless CMS (Content Management System) built with Node.js. It allows developers to quickly and easily create API-driven content management systems. Strapi provides a flexible and customizable content management interface and powerful APIs for managing content, making it an ideal choice for building dynamic web applications.
Pagination is a technique that helps users work with a large amount of data by dividing it into smaller chunks or pages. It enhances the general user experience by limiting the amount of data loaded at a given time, improving its speed and the time it takes to load.
Infinite scrolling is when content loads as the user scrolls down the page without clicking buttons or links for page navigation. It gives a smooth and engaging browsing experience by loading new content as the user reaches the end of the content displayed. Infinite scrolling is especially effective in applications that contain a massive amount of content and whose volume increases constantly, such as social networks or news portals.
Integrating Astro.js with Strapi 5 merges the strengths of both platforms. Astro's performance-oriented frontend can consume dynamic content served by Strapi's API. By integrating Astro with Strapi, you can fetch content at build time or runtime, offering flexibility for static and dynamic rendering. Using Strapi as the backend provides a centralized content management system that editors can use without affecting the frontend codebase. Combining Astro.js and Strapi streamlines development and simplifies content updates, making it easier to build scalable websites.
You will need the following to get started:
To get started, create a new Strapi 5 backend project by running the command below:
1
npx create-strapi-app@rc my-project --quickstart
The above command will scaffold a new Strapi Node.js project and install the required dependencies.
Now, fill out the forms to create your administrator user account.
Next, create a new Astro application by running the command below:
1
npm create astro@latest
The above command will prompt you to select where you want to create your new project. Press the ESC key to use the default. Start your project with the Empty template, select no write in Javascript, install dependency, and initialize the new git repository. Overall, your selection should look like the screenshot below:
To demonstrate how pagination and infinite scrolling work, create a new Article collection, add some entries, and publish them so that the API for your content can be consumed.
The Article collection will have the following data types:
title
: The article's titledescription
: The article's descriptioncontent
: The article's contentTo access your Astro application's Article collection API, navigate to Settings > Roles > Public and select the Article collection. In the permissions tab, check to select all to give the public read, write, update, and delete access.
Now, let's connect your Strapi CMS with your Astro app. First, update your astro.config.mjs
configuration with the code:
1
2
3
4
5
6
7
8
import { defineConfig } from 'astro/config';
export default defineConfig({
env: {
STRAPI_API_URL: process.env.STRAPI_API_URL,
},
output: 'hybrid'
});
This configuration:
STRAPI_API_URL
environment variables.Then add STRAPI_API_URL
variable to your .env
file:
STRAPI_API_URL=http://localhost:1337
With the collection type and Astro configurations created to connect with your Astro app, let's load some sample data to the Articles collection. Create a populate-strapi.js
file and add the code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import axios from "axios";
const STRAPI_URL = "http://localhost:1337";
async function populateArticles() {
try {
const { data: posts } = await axios.get(
"https://jsonplaceholder.typicode.com/posts"
);
for (const post of posts) {
await axios.post(
`${STRAPI_URL}/api/articles`,
{
data: {
title: post.title,
content: post.body,
},
},
{
headers: {
"Content-Type": "application/json",
},
}
);
console.log(`Created article: ${post.title}`);
}
console.log("All articles created successfully!");
} catch (error) {
console.error("Error:", error.response?.data || error.message);
}
}
populateArticles();
Here, we defined a populateArticles()
to fetch sample data from jsonplaceholder and send a POST request to Strapi CMS to save them in the Article collection.
Run the command below the code in the populateArticles
function:
node populate-strapi.js
If everything works as expected, you should see All articles created successfully!
printed on your terminal.
Now that you've loaded some sample data on your Article collection let's fetch paginated data from Strapi. Create a new pagination.astro
file in the pages
directory. Then, add the code below to configure the Javascript fetch API to fetch data from your Strapi CMS.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
---
import "../styles/articles.css";
export const prerender = false;
const currentPath = Astro.url.pathname;
const page = parseInt(Astro.url.searchParams.get("page") || "1");
const pageSize = 6;
const fetchArticles = async (page, pageSize) => {
const response = await fetch(
`${import.meta.env.STRAPI_API_URL}/api/articles?pagination[page]=${page}&pagination[pageSize]=${pageSize}&populate=*`
);
const data = await response.json();
return data;
};
const { data: articles, meta } = await fetchArticles(page, pageSize);
---
The above code will:
prerender
to false
, indicating that this page should be server-side rendered.pageSize
of 6 articles per page.fetchArticles
that makes a fetch request for articles at the Strapi REST API endpoint. The data returned from this function includes the articles and metadata, such as the current page number and the total number of pages available (meta.pagination.page
and meta.pagination.pageCount
).
page
and pageSize
).STRAPI_API_URL
environment variable to construct the full API URL.
Requests all populated fields with the populate=*
parameter.fetchArticles
function with the current page
and pageSize
.articles
data and meta
information.Then render the articles and add buttons to navigate between the articles pages:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<div class="articles-container">
<h1>Latest Articles</h1>
<div class="articles-grid">
{
articles.map((article) => (
<article class="article">
<h2>{article.title}</h2>
<div class="article-content" set:html={article.content} />
</article>
))
}
</div>
<nav class="pagination">
<a
href={`${currentPath}?page=${meta.pagination.page - 1}`}
class={`pagination-button ${page <= 1 ? "disabled" : ""}`}
aria-label="Go to previous page"
rel={page > 1 ? "prev" : null}
>
Previous
</a>
<span class="pagination-info">
Page {page} of {meta.pagination.pageCount}
</span>
<a
href={`${currentPath}?page=${meta.pagination.page + 1}`}
class={`pagination-button ${page >= meta.pagination.pageCount ? "disabled" : ""}`}
aria-label="Go to next page"
rel={page < meta.pagination.pageCount ? "next" : null}
>
Next
</a>
</nav>
</div>
To add some basic styling to the app, create a styles
directory in the root of your project and create an articles.css
file. Copy the styles here and add to the articles.css
file. Then, on your browser, navigate to /pagination
. You can see all the articles displayed on page 6 and navigate between pages by clicking the next and previous page buttons.
In the previous section, you saw how to add pagination to your Astro application and tested it to see how it improves app load time and user experience. Let's move further to implement infinite scrolling, which helps you achieve the same result of increasing user experience and data load time.
First, the api
folder is in the pages
directory. In the api
directory, create a new articles.js
file. This will be where your application will ask for articles it needs from the server. Add the code snippet below:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
export async function GET({ url }) {
const page = url.searchParams.get("page") || "1";
const pageSize = url.searchParams.get("pageSize") || "10";
const response = await fetch(
`${
import.meta.env.STRAPI_API_URL
}/api/articles?pagination[page]=${page}&pagination[pageSize]=${pageSize}&populate=*`
);
const data = await response.json();
return new Response(
JSON.stringify({
articles: data.data,
hasMore: data.meta.pagination.page < data.meta.pagination.pageCount,
totalPages: data.meta.pagination.pageCount,
}),
{
headers: { "Content-Type": "application/json" },
}
);
}
The code snippet sets up an API endpoint to fetch articles from a Strapi server. It defines a GET
function to retrieve articles based on pagination parameters like page number and page size. The function sends a request to the Strapi server, receives a list of articles and information about pagination, and checks if there are more pages of articles available. Finally, it formats the list of articles and pagination information and sends it back in a standardized JSON response.
Now, create a new file in your pages
directory named infinite-scroll.astro
and add the code.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
---
import "../styles/articles.css";
export const prerender = false;
const initialPage = parseInt(Astro.url.searchParams.get("page") || "1");
const pageSize = 10;
const fetchArticles = async (page) => {
const response = await fetch(
`${import.meta.env.STRAPI_API_URL}/api/articles?pagination[page]=${page}&pagination[pageSize]=${pageSize}&populate=*`
);
const data = await response.json();
const { data: fetchedArticles, meta } = data;
return {
articles: fetchedArticles,
hasMore: meta.pagination.page < meta.pagination.pageCount,
totalPages: meta.pagination.pageCount,
};
};
const {
articles: initialArticles,
hasMore: initialHasMore,
totalPages,
} = await fetchArticles(initialPage);
---
The above code loads initial data when the page is first loaded. This ensures the page has some content to display before user interactions occur. Next, add the code below to display the content on the webpage and handle the infinite scrolling functionalities.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
<div class="articles-container">
<h1>Latest Articles</h1>
<div class="articles-grid">
{
initialArticles.map((article) => (
<article class="article">
<h2>{article.title}</h2>
<div class="article-content" set:html={article.content} />
</article>
))
}
</div>
<div id="article-loader" style="display: none;">Loading more articles...</div>
</div>
<script define:vars={{ initialHasMore, initialPage, pageSize, totalPages }}>
let isLoading = false;
let hasMore = initialHasMore;
let currentPage = initialPage;
const articleLoader = document.getElementById("article-loader");
const articlesGrid = document.querySelector(".articles-grid");
const fetchMoreArticles = async () => {
if (isLoading || !hasMore) return;
isLoading = true;
articleLoader.style.display = "block";
try {
const response = await fetch(
`/api/articles?page=${currentPage + 1}&pageSize=${pageSize}`
);
const data = await response.json();
if (!data.articles || data.articles.length === 0) {
hasMore = false;
} else {
currentPage++;
data.articles.forEach((article) => {
const articleElement = document.createElement("article");
articleElement.className = "article";
articleElement.innerHTML = `
<h2>${article.title}</h2>
<div class="article-content">${article.content}</div>
`;
articlesGrid.appendChild(articleElement);
});
hasMore = currentPage < totalPages;
}
console.log(
`Fetched page ${currentPage}/${totalPages}, hasMore: ${hasMore}`
);
} catch (error) {
console.error("Error fetching articles:", error);
hasMore = false;
} finally {
isLoading = false;
articleLoader.style.display = hasMore ? "none" : "block";
articleLoader.textContent = hasMore
? "Loading more articles..."
: "No more articles to load.";
if (!hasMore) {
window.removeEventListener("scroll", handleScroll);
console.log("No more articles, removed scroll listener");
}
}
};
const handleScroll = () => {
if (isLoading || !hasMore) return;
const scrollHeight = document.documentElement.scrollHeight;
const scrollTop = window.scrollY || document.documentElement.scrollTop;
const clientHeight =
window.innerHeight || document.documentElement.clientHeight;
if (scrollTop + clientHeight >= scrollHeight - 200) {
console.log(`Triggering fetch for page ${currentPage + 1}`);
fetchMoreArticles();
}
};
window.addEventListener("scroll", handleScroll);
</script>
The above code will:
<div>
with the class “articles-container” which includes a <h2>
“Latest Articles” and a grid layout for articles. isLoading
, hasMore
, currentPage
and select necessary elements from the DOM articleLoader
, articlesGrid
. fetchMoreArticles
, which will load more articles from the server when the user scrolls down the page. Within fetchMoreArticles
, to avoid multiple server requests while waiting for a response, we use the isLoading
flag, show a loading message (articleLoader
), and update the current page number (currentPage
). /api/articles
with the current page number and page size as parameters. After getting the response, we add the new articles to the articles grid and set hasMore
as true or false, depending on the availability of more articles. handleScroll
that will listen for scroll events and call the function fetchMoreArticles
when the user scrolls down to the bottom of the page. addEventListener
method to the window object to trigger the handleScroll
function when the user scrolls. Now, navigate to the /infinite-scroll
endpoint on your browser to see the infinite scrolling feature in action.
You can find the code for the front end of this tutorial in this GitHub repository. Clone and start the Astro development server by running the command below:
npm run dev
By integrating pagination and infinite scrolling into your Astro.js application with Strapi 5, you've enhanced the user experience for content-rich sites by allowing efficient data loading and providing a smoother navigation experience.
Pagination and infinite scrolling should be included in any application involving a lot of data, such as a blog, news app, or social media app. The issue here is identifying when to apply each of the two methods. Pagination may be more applicable if you work with many data sets where users usually want to go to a specific page. However, if the aim is to give the users the functionality of browsing through the page and loading more content as they scroll down, then infinite scrolling can be more applicable. Finally, the decision depends on your application's specific requirements and user experience goals.
We covered how to set up pagination by fetching data from Strapi's API with pagination parameters and displaying it in Astro.js. We also implemented infinite scrolling using client-side JavaScript to load more content as the user scrolls, combining static site generation with dynamic data loading.
For further improvement, consider adding error handling and loading indicators to improve user experience. You might also explore alternatives like a "Load More" button for better accessibility or implement virtual scrolling for handling extremely large datasets.
To deepen your understanding, refer to tutorials like Implementing Pagination and Infinite Scrolling in Astro.js for detailed examples. Exploring the official documentation for Astro.js and Strapi 5 can also provide valuable insights for further development.
I'm a Software Engineer and Technical Writer who specializes in Node.js and JavaScript. I'm passionate about technology and sharing knowledge.