Nextjs static blog with pagination - using Prismic

hi guys,
we have been trying to build a very simple thing - a static site using nextjs with pagination. The content is coming from prismic.

However we have been struggling a LOT to get it done. Its very unclear on how to do this - how to generate page numbers, how to generate slugs, etc .
I think we are unable to figure out how to fit a CMS like prismic with a static site generator like nextjs.
We are obviously trying getstaticprops and getstaticpaths of Nextjs without success.

It would be great to get a simple example that does this. I have a messy codebase that doesnt work...but it would be so much nicer if we get an extremely simple example from you guys.

the only example that seems close (but we cant get it to work is GitHub - gustavoguichard/vidanatural)

One thing i want to mention is that your own example on your site is now considered deprecated -
we should be using getstaticpaths and getstaticprops instead of getinitialprops.

there's an example here - next.js/examples/cms-prismic at canary · vercel/next.js · GitHub - but no pagination which completely kills the whole purpose of using a CMS

P.S. it would so great if you could use Apollo Client way to do this - we have a pretty large codebase that we are planning to move to using nextjs and prismic and we already use apollo.

[EDIT: Removed some posts to shorten thread.]

I've made it one of my priorities to create an example, in the meantime there's some info in the react docs about pagination:

I'll get back to you as soon as I've found time to make my example.

Thanks.

@Phil Looking forward to seeing how you will integrate it. I'm just about to do it as well, and I might end up writing a tutorial about it.

It's very useful that once you paginate the results, the response object really useful attributes to help you see how many pages are there in total and on which page are you at the moment. With the article that you linked above, it shouldn't be too hard to build a full pagination feature for your Prismic blog.

Example response object:

license: "All Rights Reserved"
next_page: "https://kontist-demo.cdn.prismic.io/api/v2/documents/search?ref=YKaKehAAAB8AicM4&q=%5B%5Bat%28document.type%2C+%22blog_post%22%29%5D%5D&orderings=%5Bmy.blog_post.publication_date+desc%5D&page=2&pageSize=5"
page: 1
prev_page: null
results: (5) [{…}, {…}, {…}, {…}, {…}]
results_per_page: 5
results_size: 5
total_pages: 88
total_results_size: 437
version: "f0ce00b"
1 Like

Hi Everyone (@kris, @lee, @sjcgct, @sandys ),

Sorry about the delay. I've got an example for you now, based off of a previous post for Nuxt I made.

So what you need to do is make 2 extra queries in your post, being that you're using Next.js this won't slow your project down.

For the first query for your 'next' article will be to get all results on the post type, then limit the result size to 1, then you set the query option 'after' to get the only the next document.

The idea is that then you reverse order to get your 'previous' article. You do this using the orderings option.

Here's what you're 2 queries should look like:

const prevpost = (await client.query(Prismic.Predicates.at('document.type', 'post'), { pageSize : 1 , after : `${post.id}`, orderings: '[my.post.date desc]'})).results[0] || 'undefined'

const nextpost = (await client.query(Prismic.Predicates.at('document.type', 'post'), { pageSize : 1 , after : `${post.id}`, orderings: '[my.post.date]'})).results[0] || 'undefined'

You'll then return the results to the template and pass them through the link resolver and use the Next-link component like so:

{prevpost !== 'undefined'
  ? (
    <NextLink as={linkResolver(prevpost)} href={hrefResolver(prevpost)}>
      <a>Previous Post</a>
    </NextLink>
  )
: null}
{nextpost !== 'undefined'
  ? (
    <NextLink as={linkResolver(nextpost)} href={hrefResolver(nextpost)}>
      <a>Next Post</a>
    </NextLink>
  )
: null}

You can see it's best to do a check to remove the link if there are no articles to link.

Here is my full example based on our sample Next.js blog:

import React from "react";
import Head from "next/head";
import Prismic from '@prismicio/client'
import { default as NextLink } from 'next/link'
import { RichText } from "prismic-reactjs";

import { queryRepeatableDocuments } from 'utils/queries'

// Project components
import DefaultLayout from "layouts";
import { BackButton, SliceZone } from "components/post";

// Project functions & styles
import { Client } from "utils/prismicHelpers";
import { postStyles } from "styles";
import { hrefResolver, linkResolver } from 'prismic-configuration'

/**
 * Post page component
 */
const Post = ({ post, prevpost, nextpost }) => {
  if (post && post.data) {
    const hasTitle = RichText.asText(post.data.title).length !== 0;
    const title = hasTitle ? RichText.asText(post.data.title) : "Untitled";

    return (
      <DefaultLayout>
        <Head>
          <title>{title}</title>
        </Head>
        <div className="main">
          <div className="outer-container">
            <BackButton />
            <h1>{title}</h1>
          </div>
          <SliceZone sliceZone={post.data.body} />
          {prevpost !== 'undefined'
            ? (
              <NextLink as={linkResolver(prevpost)} href={hrefResolver(prevpost)}>
                <a>Previous Post</a>
              </NextLink>
            )
          : null}
          {nextpost !== 'undefined'
            ? (
              <NextLink as={linkResolver(nextpost)} href={hrefResolver(nextpost)}>
                <a>Next Post</a>
              </NextLink>
            )
          : null}
        </div>
        <style jsx global>
          {postStyles}
        </style>
      </DefaultLayout>
    );
  }

  return null;
};

export async function getStaticProps({ params, preview = null, previewData = {} }) {
  const { ref } = previewData
  const client = Client()
  const post = await client.getByUID("post", params.uid, ref ? { ref } : null) || {}
  const prevpost = (await client.query(Prismic.Predicates.at('document.type', 'post'), { pageSize : 1 , after : `${post.id}`, orderings: '[my.post.date desc]'})).results[0] || 'undefined'
  const nextpost = (await client.query(Prismic.Predicates.at('document.type', 'post'), { pageSize : 1 , after : `${post.id}`, orderings: '[my.post.date]'})).results[0] || 'undefined'
  return {
    props: {
      preview,
      post,
      prevpost,
      nextpost
    }
  }
}

export async function getStaticPaths() {
  const documents = await queryRepeatableDocuments((doc) => doc.type === 'post')
  return {
    paths: documents.map(doc => `/blog/${doc.uid}`),
    fallback: true,
  }
}

export default Post;

I'll add this to our documentation and create an example in Slice Machine/

Let me know if you have any questions.

Thanks.

1 Like

Thanks, that helps alot.

I was looking to make a listing page for my blogs (i.e. list out ~10 blogs per page) but what you've shown there helped me figure it out.

No worries. Feel free to share your finished example for your use case here to help future users who are looking to do that. :slight_smile:

Sure thing...

I created a new page at /pages/blog-articles/[page].js with the following getStaticPaths and getStaticProps

export async function getStaticPaths({ preview = null, previewData = {}}) {
  const { ref } = previewData
  const client = Client()
  const posts = await client.query(
    Prismic.Predicates.at("document.type", "post"), {
      orderings: "[my.post.date desc],", page: 1, pageSize: 6,
      ...(ref ? { ref } : null)
    },
  )

  return {
    paths: Array.from(Array(posts.total_pages).keys())
      .map(x => ({ params: { page: `${x + 1}`, total_pages: posts.total_pages } })),
    fallback: true,
  }
}

// Fetch content from prismic
export async function getStaticProps({ preview = null, previewData = {} }) {
  const { ref } = previewData
  const client = Client()
  const doc = await client.getByUID('page', 'blog-articles', ref ? { ref } : null) || {}

  let posts
  let results = []
  let page = 1
  do{
    posts = await client.query(
      Prismic.Predicates.at("document.type", "post"), {
        pageSize : 100, page : page,
        orderings: '[document.first_publication_date desc]',
        ...(ref ? { ref } : null)
      },
    )
    results.push(...posts?.results)
    page++
  } while(page <= posts.total_pages)

  return {
    props: {
      ...doc,
      slices: doc ? doc.data['body'] : [],
      posts: results,
      preview
    }
  }
}

My blog-articles page is of type "page" and uses slice machine for content above the listing (it would actually be really useful to be able to have multiple slice-zones tbh - if you take feedback here).

I use react-paginate to generate the pagination when there's more than one page.

1 Like

Awesome! This is great thank you :grin:

Actually, multiple Slice Zones actually exist you just have to have them in separate tabs in your Custom Type.

Every day's a school-day :exploding_head:

1 Like

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.

Hi Prismic Team!

I have a blog that I'm building with NextJS using server side rendering. I have a blog homepage that lists the 30 most recent posts. I'd like to have pagination at the bottom of the page that allows the user to navigate backwards / forwards 20 posts listed on a page at a time.

I found another post that discusses this but it just navigates 1 post at a time (and its static not SSR) Nextjs static blog with pagination - using Prismic - #14 by Phil

The code you have for nextpost & prevpost make sense to me - just wondering how you would do that 20 posts at a time. Also wondering how to handle this displaying on a different page template. Something like mywebsite.com/blog/previous etc?

Here's my current blog homepage code in a gist https://gist.github.com/nikibrown/acee56673c7e6257bbb97482b87d4dd7


import Prismic from 'prismic-javascript'
import { default as NextLink } from 'next/link'
import { linkResolver } from '../../prismic-configuration'
import { client } from '../../prismic-configuration'
import BlogHero from '../../components/BlogHero'
import Card from '../../components/Card'
import BlogFilter from '../../components/BlogFilter'
import Header from '../../components/Header'
import Navbar from '../../components/Navbar'
import Footer from '../../components/Footer'
import Pagination from '../../components/Pagination'

function Home( {posts, featuredPosts, prevPosts} ) {

	return (
		<>
			<Header 
				pageTitle="Blog" 
				metaDesc="Learn from industry experts and customers in construction, energy, agriculture, and more.
Get product updates and news from the DroneDeploy team."
				metaKeywords="Drone Mapping Software, Drone Mapping App, UAV Mapping, Surveying Software"
				ogImg="https://www.dronedeploy.com/img/dronedeploy-logo-rendered-whitebg.png"
			/>
			<Navbar />
			<BlogHero featuredPosts={featuredPosts} />
			<BlogFilter />
			<main>
				<div className="container">
					<div className="row">
						{posts.map((post, index) => (
							<div className="col-lg-4 col-sm-12 col-xs-12 mb-4" key={index}>
								<Card 
									post={post} 
									preload="false"
									cardImg={post.data.thumbnail.url} 
									cardImgWidth={post.data.thumbnail.dimensions.width}
									cardImgHeight={post.data.thumbnail.dimensions.height} 
								/>
							</div>
						))}

					</div>
					<div className="row">
						<div className="col-lg-12">
							<NextLink as={linkResolver(prevPosts)} href={linkResolver(prevPosts)}>
								<a>Previous Posts</a>
							</NextLink>					
						</div>
					</div>
				</div>
			</main>
			<Footer />
		</>

	)
}


export async function getServerSideProps({ res }) {

	const allPosts = await client.query(
        [
            Prismic.Predicates.at('document.type', 'blog_post'),
			Prismic.Predicates.not('my.blog_post.status', '9 - Promoted')
        ],
        { 
			orderings: '[my.blog_post.published desc]',
			pageSize : 20
		}
    )

	const posts = allPosts.results;

	const allFeaturedPosts = await client.query(
        [
        	Prismic.Predicates.at('document.type', 'blog_post'),
        	Prismic.Predicates.at('my.blog_post.status', '9 - Promoted')
        ],
        { 
			orderings: '[my.blog_post.published desc]',
			pageSize : 3
		}
    )

	const featuredPosts = allFeaturedPosts.results

	const prevPosts = (await client.query(
		Prismic.Predicates.at('document.type', 'blog_post'), 
		{ pageSize : 20 , 
		 	// get ID of last blog post on the page
			after : `${posts[posts.length -1].id}`, 
			orderings: '[my.post.date desc]'
		}
	))

	// Not sure I need this anymore 🔽
    res.setHeader('Cache-Control', 's-maxage=1, stale-while-revalidate')

	return { props: { posts , featuredPosts, prevPosts} }
}

export default Home

Any pointers would be greatly appreciated!

Hi Niki,

To get 20 results at a time just change the pageSize attribute to 20

  const prevpost = (await client.query(Prismic.Predicates.at('document.type', 'post'), { pageSize : 20 , after : `${post.id}`, orderings: '[my.post.date desc]'})).results[0] || 'undefined'
  const nextpost = (await client.query(Prismic.Predicates.at('document.type', 'post'), { pageSize : 20 , after : `${post.id}`, orderings: '[my.post.date]'})).results[0] || 'undefined'

Although I'm not sure what you mean by:

Can you explain in more detail?

Thanks.

Hi @Phil Thanks for the quick reply.

This sort of makes sense but I'm running into the issue of my nextPosts query not actually moving forward in the list of posts. It seems to just continue going backwards through my list of posts just like my previous posts query. Removing desc from my orderings seems to do nothing. See code below:


import Prismic from 'prismic-javascript'
import { default as NextLink } from 'next/link'
import { client } from '../../prismic-configuration'
import Card from '../../components/Card'
import Header from '../../components/Header'
import Navbar from '../../components/Navbar'
import Footer from '../../components/Footer'

function Previous( {allPrevPosts, allNextPosts, query} ) {


	let posts
	
	// set posts to previous or next posts query
	query.prevPostsBool ? posts = allPrevPosts : posts = allNextPosts 

	const lastPostID = posts[posts.length -1].id;

	const firstPostID = posts[0].id;

	return (
		<>
			<Header 
				pageTitle="Blog" 
				metaDesc="Learn from industry experts and customers in construction, energy, agriculture, and more.
Get product updates and news from the DroneDeploy team."
				metaKeywords="Drone Mapping Software, Drone Mapping App, UAV Mapping, Surveying Software"
				ogImg="https://www.dronedeploy.com/img/dronedeploy-logo-rendered-whitebg.png"
			/>
			<Navbar />
	
			<main>
				<div className="container">
					<div className="row">


						{posts.map((post, index) => (
							<div className="col-lg-4 col-sm-12 col-xs-12 mb-4" key={index}>
								{/* TODO: make these components more reusable by just passing data? Like <Header /> and having fallbacks */}
								
								<Card 
									post={post} 
									preload="false"
									cardImg={post.data.thumbnail.url} 
									cardImgWidth={post.data.thumbnail.dimensions.width}
									cardImgHeight={post.data.thumbnail.dimensions.height} 
								/>
								<p>Post ID: {post.id}</p>
							</div>
						))}


					</div>
					<div className="row">
						<div className="col-lg-12">

							<h1>Last Post ID: {lastPostID}</h1>
							<h1>First Post ID: {firstPostID}</h1>
							<nav aria-label="Page navigation example">
								<ul className="pagination">
									<li className="page-item">
										<NextLink 
											href={{
												pathname: "/blog/previous",
												query: {
													id: lastPostID,
													prevPostsBool: true,
													nextPostsBool: false
												}
											}}

											as={"/blog/previous"}
										>
											<a className="page-link" href="#">&laquo; Previous Posts</a>
										</NextLink>
									</li>
									<li className="page-item">
										<NextLink 
											href={{
												pathname: "/blog/previous",
												query: {
													id: lastPostID,
													prevPostsBool: false,
													nextPostsBool: true
												}
											}}

											as={"/blog/next"}
										>
											<a className="page-link" href="#">Next Posts &raquo;</a>
										</NextLink>
									</li>	
								</ul>
							</nav>		
							
						</div>
					</div>
					
				</div>
			</main>
			<Footer />
		</>

	)
}



export async function getServerSideProps({ query, res  }) {
	console.log("query.id " + query.id);


	const prevPosts = await client.query(
		Prismic.Predicates.at('document.type', 'blog_post'), 
		{ pageSize : 3 , 
			after : `${query.id}`, 
			orderings: '[my.blog_post.published desc]'
		}
	)

	const allPrevPosts = prevPosts.results

	const nextPosts = await client.query(
		Prismic.Predicates.at('document.type', 'blog_post'), 
		{ pageSize : 3 , 
			after : `${query.id}`, 
			orderings: '[my.blog_post.published]'
		}
	)

	const allNextPosts = nextPosts.results

	

	// Not sure I need this anymore 🔽
    res.setHeader('Cache-Control', 's-maxage=1, stale-while-revalidate')

	return { props: { allNextPosts, allPrevPosts, query} }
}

export default Previous

Strange it works in my test:

Query for all posts (second post uid: who-were-the-busby-babes)

Query for previous/desc post with second post id (uid: george-best-the-man-the-myth-the-legend)

Query for next posts with second post id (uid of next post ole-gunnar-solskjaer-on-the-goal-in-1999-which-made-him-a-hero)

These are the expected results, try testing this in your API Browser to see if it works.