TypeError: Cannot read property 'title' of undefined NextJS

Viewed 1740

I've been trying to build this blog app, Before is fine and in the dev environment is also fine. But, as soon as i try to build it for production it caught in this error.

Error occurred prerendering page "/blogs/[slug]". Read more: https://nextjs.org/docs/messages/prerender-error TypeError: Cannot read property 'title' of undefined

I am confused because this app had been build before and nothing is wrong. This is the culprit:

import Head from "next/head";
import "bootstrap/dist/css/bootstrap.min.css";
import ReactMarkdown from "react-markdown";
import moment from "moment";

const Post = ({ posts }) => {
  return (
    <>
      <Head>
        <title>{posts && posts.title}</title>
        <meta
          name="viewport"
          content="width=device-width, initial-scale=1.0"
        ></meta>
      </Head>
      <div className="container" style={{ marginTop: "3.85rem" }}>
        <div className="row">
          <h1>{posts.title}</h1>
        </div>
        <div className="row d-flex justify-content-end">
          <p style={{ textAlign: "right" }}>
            {moment(posts.dateTime).format("dddd, Do MMMM YYYY, hh:mm:ss a")}
          </p>
        </div>
        <br />
        <div className="row d-flex justify-content-center">
          <img
            className="postThumbNail"
            src={posts.thumbnail.url}
            alt={posts.thumbnail.url}
          />
        </div>
        <br />
        <ReactMarkdown>{posts.body}</ReactMarkdown>
      </div>
    </>
  );
};

export async function getStaticProps({ params: { slug } }) {
  const res = await fetch(
    `https://someapi.com/blogs?slug=${slug}`
  );
  const blogPost = await res.json();

  return {
    props: {
      posts: blogPost[0],
    },
    revalidate: 600,
  };
}

export async function getStaticPaths() {
  // Get Post From Strapi
  const res = await fetch(
    "https://someapi.com/blogs"
  );
  const post = await res.json();
  //  Retrun Enrich Content
  return {
    paths: post.map((posts) => ({
      params: { slug: String(posts.slug) },
    })),
    fallback: true,
  };
}

export default Post;
3 Answers

Solution 1 You need to check if attribute exists first with optional chaining :

  <h1>{posts?.title}</h1>

Solution 2 or object is not null return null until data loads

if(!posts) return null

return <div> ...Code </div>
    <Head>
        <title>{posts && posts.title}</title>
        <meta
          name="viewport"
          content="width=device-width, initial-scale=1.0"
        ></meta>
      </Head>
      <div className="container" style={{ marginTop: "3.85rem" }}>
        <div className="row">
          <h1>{posts && posts.title}</h1> // You've just forgot that if posts exist condition
        </div>
        <div className="row d-flex justify-content-end">
          <p style={{ textAlign: "right" }}>
            {moment(posts.dateTime).format("dddd, Do MMMM YYYY, hh:mm:ss a")}
          </p>
        </div>
        <br />
        <div className="row d-flex justify-content-center">
          <img
            className="postThumbNail"
            src={posts.thumbnail.url}
            alt={posts.thumbnail.url}
          />
        </div>
        <br />
        <ReactMarkdown>{posts && posts.body}</ReactMarkdown>
      </div>

The issue occurs because you're using fallback: true in getStaticPaths, and you're not handling the fallback page which gets served by Next.js before getStaticProps finishes running. At that point the props passed to the page component will be empty.

In the “fallback” version of a page:

  • The page’s props will be empty.
  • Using the router, you can detect if the fallback is being rendered, router.isFallback will be true.

— Next.js, Fallback Pages documentation


#1 Handle fallback page with router.isFallback

You can properly handle the fallback version of the page using router.isFallback.

const Post = ({ posts }) => {
    const router = useRouter()

    // Display loading until `getStaticProps()` finishes running, and populates the props.
    if (router.isFallback) {
        return <div>Loading...</div>
    }

    return (
        <h1>{posts.title}</h1>
    );
};

#2 Use fallback: 'blocking' instead

As an alternative, if you don't really need the fallback functionality, you can change the fallback value in getStaticPaths to fallback: 'blocking'. This will still generate new pages like fallback: true, but will wait until getStaticProps has ran before rendering the page.

export async function getStaticPaths() {
    const res = await fetch("https://someapi.com/blogs");
    const post = await res.json();

    return {
        paths: post.map((posts) => ({
            params: { slug: String(posts.slug) }
        })),
        fallback: 'blocking'
    };
}
Related