Skip to content

Getting TypeError on prerendering page in npm run build

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!

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).