Note: I'm setting the page's type by directly assigning it
(const page: Page<CollectionEntry<'blog'>> = ...
)
That won't work in recent versions of Astro because if we don't define type Props
, Astro.props will be a record of type Record<string, unknown>
and we can't cast an unknown
to the Page
type.
To fix this, we need to define type Props
and set the page type in it, instead of casting the page
constant to it. See the code here.
When you have tens or hundreds of posts in your blog it can be a good user experience to paginate posts so they can be browsed.
Astro gives us an API to implement pagination easily. To do this we will convert our index page to a dynamic page by renaming to [page].astro
.
Now that you have a dynamic page, you'll need to export a getStaticPaths
function that will define what pages are rendered. When we utilize the GetStaticPaths
type on this function you'll notice that Astro gives us a paginate
function we can use.
We'll return a call to paginate
from getStaticPaths
and set the blog posts and page size we want to paginate on. This will return a page to us from Astro.props.page
that will have all the necessary controls for building pagination like next and previous urls as well as the current pages data.
To see all the data available to you on the page you can pull in the Page
generic type from Astro and pass in Page<CollectionEntry<'blog'>>
. From here we'll use this data to render the proper amount posts with conditionally rendered pagination control down below.
Instructor: [0:00] After the last challenge, we now have a block index page that lists all of our blog posts. What if we end up writing tens of posts, or maybe even hundreds of posts?
[0:09] We would need to implement a pagination system in order to make it easier for our viewers to browse our posts. Luckily, Astro provides us with the tools we need to implement pagination. To implement pagination to our block index page, we need to convert it to a dynamic page.
[0:25] Right-click, rename from index, we're going to make it a dynamic page, and we'll name its dynamic segment as page. Since we have a dynamic page now, we need to export an async function, getStaticPaths, just like we did in the previous lessons. This time, we're going to obtain the paginate method from the arguments.
[0:47] Since we don't have a type on this yet, we can't know that the paginate method exists. To fix that, we can import the getStaticPaths from Astro. Of course, since it's a type, we can import it as a type. To use it on our getStaticPaths, we need to convert this into a const and turn it into an arrow function. Now we have the opportunity to set the type to getStaticPaths. There we go.
[1:12] Now, if we open the IntelliSense, we're going to see that we have all of the available methods, which in our case is just the RSS method.
[1:20] Don't worry about the error. That's because we're not returning the expected result. Since we're going to be paginating on the posts, let's bring in the post variable inside and also turn our getStaticPaths into an async function.
[1:35] To use the paginate function, we just need to return paginate, pass our posts array, and then optionally pass an object as the second argument with the property page size set to three, for example.
[1:50] Our getStaticPaths function will now generate as much pages as needed to fit three blog posts per page. For example, if we had 30 blog posts, it will generate 10 pages, and it will set the dynamic page segment that we have in our name to the number of page.
[2:06] What's cool about the paginate method is that it's going to provide us with all the data we need for the currently displayed page. That's what are the blog posts for that page, what is the current page, how many pages are there, etc. We're going to use that.
[2:22] Let's exit the getStaticPaths method and obtain the page through astro.props.page. Have in mind that we don't have any typings just yet, so if we do page., we won't see all the available data. In order to fix that, we need to bring in the type, which is the type page from the Astro package.
[2:42] Aside from the page type, let's also bring in the collection entry type from Astro content. We're going to need this because the page type is a generic type, so we're going to define the page as a collection entry of blog in order to get the full tabs for our page.
[2:58] Let's scroll down to the page declaration and set its type to page of the type collection entry and then pass in blog. Let's make sure that we have everything. There we go, we have current page, we have the data, we have what is the end page, what is the last page, the size of the page, start, total, URL, etc.
[3:20] The data is going to be our array of posts. If we scroll down, we're going to see that the posts list is throwing an error because we don't have the post defined anymore. To fix this, we can pass the page.data, which as I mentioned, is the blog post array.
[3:38] To visit our new page, we need to change the URL to /blog/1. This is going to give us the blog post on the first page. Before we proceed, it's also a good idea to change the blog link in the header to go to /blog/1.
[3:54] Let's simulate now a big number of posts. As we mentioned, we set the page size to three posts per page, but we have just two. To make it simple, I'm just going to copy-paste the blog content multiple times.
[4:08] I created 10 dummy blog posts just by copy-pasting one of the previous two that we had. Now we can see there's a third post, and also if we go to /blog/2, we're going to see a different set of posts. Again, if we go to the third page, different set of posts.
[4:26] Finally, on the fourth page, we're going to see just one post, which is the tenth post in our case. Likewise, if we go to the home page, we're going to see all 10 of our blog posts because we're bringing in all of them. It's a good idea to only show the latest three posts so this list doesn't grow and ruin our website, but I'm going to leave that up to you.
[4:46] Now we're ready to implement the pagination controls. Let's create a div below our posts list and give it a class. This is going to be the container in which we're going to keep three elements. That's the previous page, which is going to be a link, the current page, which is going to be just a text, and the next page, which is also going to be a link.
[5:05] The previous page and the next page will be conditionally rendered if a previous or next page exists. First, let's separate this div from the posts list above by adding a margin-top. Let's make it a flexbox. We want to justify the content to the center and add a gap of 16 pixels. That's enough.
[5:26] Let's create another div now for our previous page. We'll set the width to 24 and we're going to align the text to end, which is going to be on the right side. As I mentioned earlier, we're going to conditionally render the link.
[5:39] If we do page.url.currentNextOrPrevious, in our case we want to do Previous because we're looking to obtain the URL of the previous page. If that exists, we are going to render a link, which we already have.
[5:58] We're going to set the href of the link to page.url.previous. We're going to set the relationship to Previous as well. Inside of the link, we can type in the less than "<" symbol, Previous. This is how we conditionally render the previous link.
[6:20] Next, we can add the page.currentsPage, and because it's going to be a text, we don't need to do anything fancy. Then we want to do the same for our next page. I'm going to copy this block from above and paste it here.
[6:35] First, let's change the text. Instead of Previous, we want to say Next. Then instead of less than, we're going to say greater than. The URL is going to point to the next page URL, and also the relationship is going to be the next relationship.
[6:52] The conditional rendering is going to be set on the next URL, not the previous one. Lastly, we need to change the text alignment to be on the text start. Let's save this.
[7:03] We're going to immediately see that we have the current page number and also the next link because we're on the first page. If we click on the next link, we're going to see the previous link, the current page, and also the next link. If we go to the fourth page, we can see that the next link is now gone, but with only the previous one being displayed, and these are our pagination controls.
[7:26] You might notice that the list always renders 1, 2, 3. Even though we go to the second page, our blog posts are still labeled as 1, 2, 3. The page also gives us the item number starting from .
[7:40] In order to fix this, first, we need to modify the Posts lists. Let's make it accept an optional start, which is going to be a number. We can destructure it above and also set it to one by default in case we don't provide it. To use this, let's go down to the order list and set its start property to be the start prop.
[8:02] Great, now we can pass the data we got from the page. To our Posts list, we can say the start is going to be the page.start and save that. Now we can see that the indexes have changed. Now we're at 6, 7, 8. If we go to the previous page, we'll see 3, 4, and 5, , 1, and 2, and that's it.
[8:23] Of course, we don't want it to start with unless we really like it to. In order to fix that, we can just append +1 to the start of our posts lists, and now we're fixed. 1, 2, 3, let's go to the next page 4, 5, 6, 7, 8, 9, and lastly, number 10. There we go, we have full pagination in our blog index page now.
[8:46] Let's do a recap. Astro comes with a built-in pagination mechanism. To paginate our blog index page, we needed to convert it to a dynamic page so we can use the GET static paths method.
[8:56] From the arguments, we obtained the paginate methods and told Astro to create the necessary pages for all of our blog posts with three posts per page. We then obtained the page from the Astro props. The page had all the info we need, the current page, the next and the previous links, the start number, and the posts for that page.
Member comments are a way for members to communicate, interact, and ask questions about a lesson.
The instructor or someone from the community might respond to your question Here are a few basic guidelines to commenting on egghead.io
Be on-Topic
Comments are for discussing a lesson. If you're having a general issue with the website functionality, please contact us at support@egghead.io.
Avoid meta-discussion
Code Problems?
Should be accompanied by code! Codesandbox or Stackblitz provide a way to share code and discuss it in context
Details and Context
Vague question? Vague answer. Any details and context you can provide will lure more interesting answers!