Brad Ahrens
Brad Ahrens

Reputation: 5168

NextJS Prerender Error - Works fine in Dev, breaks in Build

I am building a NextJS application and everything is working fine locally. It pulls in all of the data and throws no error.

However, when I try to run npm run build, I get a Prerender Error. I have tried to follow the instructions in the documentation, but I didn't find it very helpful.

Could the problem be that I'm importing the posts using path.join(process.cwd(), './posts') and in this case, when it builds, the paths are somehow different than in development? I can't think of anything else that could be missing.

package.json

{
  ...
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  ....
}

Full error:

Error occurred prerendering page "/blog/Blog". Read more: https://nextjs.org/docs/messages/prerender-error
TypeError: Cannot read property 'slug' of undefined
    at Post (/.next/server/chunks/2648.js:223:21)
    at d (/node_modules/react-dom/cjs/react-dom-server.node.production.min.js:33:498)
    at bb (/node_modules/react-dom/cjs/react-dom-server.node.production.min.js:36:16)
    at a.b.render (/node_modules/react-dom/cjs/react-dom-server.node.production.min.js:42:43)
    at a.b.read (/node_modules/react-dom/cjs/react-dom-server.node.production.min.js:41:83)
    at Object.exports.renderToString (/node_modules/react-dom/cjs/react-dom-server.node.production.min.js:52:138)
    at renderPage (/node_modules/next/dist/server/render.js:736:46)
    at Object.ctx.renderPage (/.next/server/pages/_document.js:77:26)
    at Object.defaultGetInitialProps (/node_modules/next/dist/server/render.js:368:51)
    at Function.getInitialProps (/.next/server/chunks/6859.js:651:16)
postdirectory /posts

Here is my file structure:

📦lib
 ┗ 📜posts.js
📦pages
 ┣ 📂blog
 ┃ ┣ 📂Blog
 ┃ ┃ ┗ 📜index.tsx
 ┃ ┣ 📂Hero
 ┃ ┃ ┗ 📜index.tsx
 ┃ ┗ 📜[slug].tsx
 ┣ 📂knowledge
 ┃ ┣ 📂components
 ┃ ┃ ┣ 📂BlogSection
 ┃ ┃ ┃ ┣ 📂components
 ┃ ┃ ┃ ┃ ┗ 📂ArticleBox
 ┃ ┃ ┃ ┃  ┗ 📜ArticleBox.tsx
 ┃ ┃ ┃ ┗ 📜index.tsx
 ┃ ┃ ┣ 📂Hero
 ┃ ┃ ┃ ┗ 📜index.tsx
 ┃ ┗ 📜index.tsx
 ┣ 📜_app.css
 ┣ 📜_app.tsx
 ┣ 📜_document.tsx
 ┗ 📜index.tsx
📦posts
 ┣ 📜post_1.md
 ┣ 📜post_2.md
 ┣ 📜post_3.md

pages/blog/[slug].tsx

import { getAllPostIds, getPostData, getSortedPostsData } from '../../lib/posts';
import React from 'react';
import Hero from './Hero';
import BlogSection from './Blog';

export interface Props {
    postData: {...},
    posts: {...}[]
}

const BlogArticle: React.FC<Props> = ({ postData, posts }) => {
    return (
        <>
            <Hero />
            <BlogSection postData={postData} posts={posts} />
        </>
    );
};

export default BlogArticle;

export async function getStaticPaths() {
    const paths = await getAllPostIds();
    return {
        paths,
        fallback: false
    }
}

export async function getStaticProps({ params }) {
    return {
        props: {
            postData: await getPostData(params.slug),
            posts: await getSortedPostsData()
        }
    }
}

lib/posts.js

import fs from 'fs'
import path from 'path'
import matter from 'gray-matter'

const postsDirectory = path.join(process.cwd(), './posts')

export async function getSortedPostsData() {
    const fileNames = fs.readdirSync(postsDirectory)
    const allPostsData = fileNames.map(fileName => {
        const slug = fileName.replace(/\.md$/, '')

        const fullPath = path.join(postsDirectory, fileName)
        const fileContents = fs.readFileSync(fullPath, 'utf8')

        const matterResult = matter(fileContents)

        return {
            slug,
            ...matterResult.data
        }
    })
}

export async function getAllPostIds() {
    const fileNames = fs.readdirSync(postsDirectory)

    return fileNames.map(fileName => {
        return {
            params: {
                slug: fileName.replace(/\.md$/, '')
            }
        }
    })
}

export async function getPostData(slug) {
    const fullPath = path.join(postsDirectory, `${slug}.md`)
    const fileContents = fs.readFileSync(fullPath, 'utf8')
    const { data: frontmatter, content } = matter(fileContents)

    return {
        slug,
        content,
        frontmatter
    }
}

Upvotes: 7

Views: 13849

Answers (1)

Brad Ahrens
Brad Ahrens

Reputation: 5168

Thanks @TDiblik for pointing me in the right direction. As he mentioned, there is a good example from Vercel.

One thing that I noticed that I did not do was add a way to handle the situation in which the post data was not available. In this case, I needed to add the following within the default exports for each page / component that was receiving the data.

    const router = useRouter()
    if (!router.isFallback && !post) {
        return <ErrorPage statusCode={404} />
    }

In this case, my pages/blog/[slug].tsx should look like:

import { getAllPostIds, getPostData, getSortedPostsData } from '../../lib/posts';
import React from 'react';
import Hero from './Hero';
import BlogSection from './Blog';
import { useRouter } from 'next/router'
import ErrorPage from 'next/error'

export interface Props {
    postData: {...},
    posts: {...}[]
}

const BlogArticle: React.FC<Props> = ({ postData, posts }) => {
    const router = useRouter()
    if (!router.isFallback && !postData && !posts) {
        return <ErrorPage statusCode={404} />
    }
    return (
        <>
            <Hero />
            <BlogSection postData={postData} posts={posts} />
        </>
    );
};

export default BlogArticle;

export async function getStaticPaths() {
    const paths = await getAllPostIds();
    return {
        paths,
        fallback: false
    }
}

export async function getStaticProps({ params }) {
    return {
        props: {
            postData: await getPostData(params.slug),
            posts: await getSortedPostsData()
        }
    }
}

With this change, I was able to build the project.

Upvotes: 2

Related Questions