I am prerendering this page on my Next.js application:
const router = useRouter(); if (!router.isFallback && !postData?.slug) { return <p>hmm... looks like an error</p> } const formatDate = date => { const newDate = new Date(date); return `${newDate.getDate()}/${ newDate.getMonth() + 1 }/${newDate.getFullYear()}` }; return ( <div className={styles.container}> <Head> <title>{postData.title}</title> <link rel='icon' href='/favicon.ico' /> </Head> <main className={styles.main}> {router.isFallback ? ( <h2>Loading...</h2> ) : ( <article className={blogStyles.article}> <div className={blogStyles.postmeta}> <h1 className={styles.title}>{postData.title}</h1> <p>{formatDate(postData.date)}</p> <img src={postData.featuredImage.node.sourceUrl} /> </div> <div className='post-content content' dangerouslySetInnerHTML={{ __html: postData.content }} /> </article> )} <p> <Link href={`/blog`}> <a>Back</a> </Link> </p> </main> </div> )
Using getStaticProps()
export async function getStaticProps({ params }) { const data = await getPost(params.slug); return { props: { postData: data.post } }; }
Using getStaticPaths()
export async function getStaticPaths() { const allPosts = await getAllPostsWithSlug(); if ( allPosts && allPosts.edges !== null && allPosts.edges.node !== null && allPosts.edges.length > 0 ) { return { paths: allPosts.edges.map(({ node }) => `/blog/${node.slug}`) || [], fallback: true } } }
When I run it locally it works fine but when I try to deploy it with npm run build
it gives an error just for the title
property only:
Error occurred prerendering page "/blog/[slug]". Read more: https://err.sh/next.js/prerender-error TypeError: Cannot read property 'title' of undefined
This is the part that confuses me as I don’t understand why the error is only on 1 property (postData.title) of the query and everything else loads fine.
I am using GraphQL to create the query:
export async function getPost(slug) { const data = await fetchAPI( ` fragment PostFields on Post { title excerpt slug date featuredImage { node { sourceUrl } } } query PostBySlug($id: ID!, $idType: PostIdType!) { post(id: $id, idType: $idType) { ...PostFields content } } `, { variables: { id: slug, idType: 'SLUG' } } ); return data; }
I import this function through an api.js file and use the data in the getStaticProps() function.
Any help on this would be highly appreciated, I looked for solutions online but couldn’t find any that worked. Thanks!
Advertisement
Answer
When handling a dynamic page such as /blog/[slug].jsx
you need to use getStaticPaths
in addition to getStaticProps
and router.isFallback?
as you’re using in the returned jsx above.
getStaticPaths
catches incoming possible paths — its behavior however is dependent on a fallback
key (which can have the values true
, false
, or "blocking"
)
Blocking
is identical to Server Side Rendering so it generates static HTML on demand for paths not returned by getStaticPaths
. This is cached for future use so that this on demand generation only happens once per path not handled by your getStaticPaths
function. If you set getStaticPaths
to true
then a small subset of dynamic paths will be rendered at build time and a loading indicator will be displayed to the user if a path they navigate to isn’t rendered during the initial build. Using true
is useful for large e-commerce sites or sites with a large number of dynamic paths so that the build process doesn’t take a ridiculously long time to complete. Setting getStaticPaths
to false
will result in any path not rendered during the build process to result in a 404 error if a user navigates to it. Depending on your needs, any of the above methods could be most appropriate. That said, it is important to note that "blocking"
does not require the use of router.isFallback
at all. I also suggest looking into the benefits of utilizing revalidate
with getStaticProps
.
Here is an example of using getStaticPaths
to catch incoming dynamic paths:
const AboutSlugsQueryVars: AboutSlugsVariables = { order: OrderEnum.ASC, field: PostObjectsConnectionOrderbyEnum.SLUG, first: 15 }; type DynamicPaths = { params: | { slug: string | Array<string>; } | never[]; }[]; export async function getStaticPaths( ctx: GetStaticPathsContext, pathsData: DynamicPaths ) { const q = ctx!.defaultLocale; console.log(`${q}`) const apolloClient = initializeApollo(); const { data } = await apolloClient.query<AboutSlugs, AboutSlugsVariables>({ query: ABOUT_SLUGS, variables: AboutSlugsQueryVars }); pathsData = []; if ( data && data.aboutslugs !== null && data.aboutslugs.edges !== null && data.aboutslugs.edges.length > 0 ) data.aboutslugs.edges.map(post => { if (post !== null && post.node !== null && post.node.slug !== null) { pathsData.push({ params: { slug: post.node.slug } }); } }); return { paths: pathsData, fallback: true }; }
There are many approaches to filtering getStaticPaths
, you can also use GetStaticPathsContext
to catch incoming locales
as well as the default locale
(if applicable).