Add pagination to Nuxt Content

Updated by Tom Wells on

Once your nuxt blog grows and your home page gets quite long and busy, it might be time to start looking at pagination. This tutorial will show you how to quickly and easily add pagination to your Nuxt Content blog or website to make it easier to navigate for your readers.

This tutorial will not be covering how to write or display your blog posts.

Requirements for the blog

Before getting into the tutorial as a whole, let's establish some requirements for the blog and what we want to achieve:

  • The homepage must be at the usual route ('/').
  • Next/previous buttons must only display when the next/previous page exists.
  • Nine posts per page to be displayed.
  • 404 error fallback (just in case).

We're going to achieve this by requesting ten posts per page (this is to determine whether a "next page" will exist) and remove the last post from the array. I feel this is better (and easier) than counting all posts and creating the number of pages required from the start. It's a more iterative approach.

Page structure

For this tutorial, we'll keep the page structure simple so you can visualise how this works:

root
pages/
- index.vue
- _slug.vue
- page/
-- _number.vue

To quickly explain, inside the pages folder, there is:

  • The homepage index.vue to display your latest posts.
  • The single post _slug.vue template that displays the individual post.
  • A page folder containing the _number.vue dynamic route for the next page(s) of blog posts.

Since we're using Nuxt Content for generating the blog posts, all your markdown files will live in the content/ folder in your root directory.

Adding a "Next page" button to your homepage

The first thing we want to do is add a button on the homepage when the number of posts exceeds what you want to display (in this case, nine posts will be displayed). For this to work, we'll use the limit method to, first of all, limit the number of posts retrieved. If we reach this limit, we'll add a "Next page" button.

So, here's the asyncData function in all its glory:

pages/index.vue
export default {
  async asyncData({ $content }) {
    const tenPosts = await $content()
      .only(['author', 'createdAt', 'description', 'path', 'title'])
      .sortBy('createdAt', 'desc')
      .limit(10)
      .fetch()

    const nextPage = tenPosts.length === 10
    const posts = nextPage ? tenPosts.slice(0, -1) : tenPosts
    return { nextPage, posts }
  }
}

This function is fairly self-explanatory in that we're retrieving ten posts from the content folder, sorting by the createdAt date, setting the nextPage variable to true or false if ten posts exist and if it's true, we remove the last post from the array.

To add the "Next page" button, all that's required is something like this:

pages/index.vue
<section id="next" v-if="nextPage">
  <nuxt-link to="/page/2">
    Next page
  </nuxt-link>
</section>

That's the homepage finished! This link will only appear if there are more posts than will be displayed on the homepage (hence retrieving ten and not 9).

Creating the pages

Now we're able to move from the homepage to (at least) page 2; it's time to hook up the following pages of posts. To achieve this, we can use both the limit and skip methods.

To keep this simple, here is the asyncData method for the _number.vue template:

pages/page/_number.vue
export default {
  async asyncData({ $content, params, error }) {
    const pageNo = parseInt(params.number)
    const tenPosts = await $content()
      .only(['author', 'createdAt', 'description', 'path', 'title'])
      .sortBy('createdAt', 'desc')
      .limit(10)
      .skip(9 * (pageNo - 1))
      .fetch()

    if (!tenPosts.length) {
      return error({ statusCode: 404, message: 'No posts found!' })
    }

    const nextPage = tenPosts.length === 10
    const posts = nextPage ? tenPosts.slice(0, -1) : tenPosts
    return { nextPage, posts, pageNo }
  }
}

In this template, the asyncData method additionally returns the page number (pageNo) we're currently on, which is useful in your template or head method. Here's a quick overview of what's going on here:

  • The page number is being retrieved and returned as pageNo. parseInt ensures it's an integer.
  • pageNo is being used to calculate the number of posts to skip.
  • As before, we're limiting the number of posts to ten and sorting by latest-first.
  • If no posts are found, we return a 404 error with the message "No posts found" - a useful fallback.
  • nextPage boolean is set if there are ten posts. If so, we then remove the last post from the array as before.
  • posts, pageNo and nextPage are returned for use in the page template.

Now you're retrieving your posts for this page, let's conditionally add previous and next page links:

pages/page/_number.vue
<section id="prev-next">
  <nuxt-link :to="prevLink">Prev page</nuxt-link>
  <nuxt-link v-if="nextPage" :to="`/page/${pageNo + 1}`">Next page</nuxt-link>
</section>

But wait, what's prevLink doing? Don't worry; it's not that complicated. It's a simple computed property to determine whether we need to go back to the homepage or a previous "page":

pages/page/_number.vue
export default {
  computed: {
    prevLink() {
      return this.pageNo === 2 ? '/' : `/page/${this.pageNo - 1}`
    }
  }
}

Now, you should have pagination for your posts with a 404 fallback if no posts are found and a homepage at '/'. Perfect.

Wrapping up

There are a few ways to achieve pagination with Nuxt Content. For example, you could count all your posts on the homepage to count the number of pages you need. However, once your blog grows to a large size (100+ posts), this could become cumbersome and slow. This method feels more iterative and works well, producing only the number of pages required without needing to count all your posts.

As you can see, the Nuxt Content module gives you everything you need to create a fully-featured blog. Adding pagination is relatively painless, and can be achieved without the need for a database resulting in a fast, SEO-friendly blog.

If you found this useful or if you have any feedback or questions, feel free to follow or comment on Twitter.