Reputation: 5168
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
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