I'm trying to deploy a blog written with Next.js v15.3.3 with Vercel but I keep getting the same error:
Error occurred prerendering page "/post/1". Read more: https://nextjs.org/docs/messages/prerender-error
TypeError: Cannot read properties of null (reading 'useState')
at exports.useState (/vercel/path0/node_modules/react/cjs/react.production.js:530:33)
at MDXRemote (file:///vercel/path0/node_modules/next-mdx-remote/dist/index.js:13:51)
at nF (/vercel/path0/node_modules/next/dist/compiled/next-server/app-page.runtime.prod.js:76:46843)
at nH (/vercel/path0/node_modules/next/dist/compiled/next-server/app-page.runtime.prod.js:76:48618)
at nW (/vercel/path0/node_modules/next/dist/compiled/next-server/app-page.runtime.prod.js:76:67762)
at nz (/vercel/path0/node_modules/next/dist/compiled/next-server/app-page.runtime.prod.js:76:65337)
at nY (/vercel/path0/node_modules/next/dist/compiled/next-server/app-page.runtime.prod.js:76:71193)
at nH (/vercel/path0/node_modules/next/dist/compiled/next-server/app-page.runtime.prod.js:76:60968)
at nW (/vercel/path0/node_modules/next/dist/compiled/next-server/app-page.runtime.prod.js:76:67762)
at nz (/vercel/path0/node_modules/next/dist/compiled/next-server/app-page.runtime.prod.js:76:65337)
Export encountered an error on /post/[slug]/page: /post/1, exiting the build.
⨯ Next.js build worker exited with code: 1 and signal: null
Error: Command "npm run build" exited with 1
I have managed to successfully deploy one version of my project, but I am unsure as to what I did differently to make it work. All attempts before and after resulted in the same issue. Now whenever I try to update the project it keeps repeating this.
Here is 'post/[slug]/page.tsx'
import { MdxContent } from '@/app/components/MdxContent'
import { getPostBySlug, getAllPosts } from '@/lib/posts'
import { PostFull } from '@/lib/types';
import type { Metadata } from 'next'
type Params = Promise<{ slug: string }>
export async function generateMetadata({ params }:{params: Params}): Promise<Metadata> {
const { slug } = await params;
const post: PostFull = await getPostBySlug(slug)
return {
title: `${post.title} | My Personal Space`,
description: post.subtitle || "Blog post",
}
}
export async function generateStaticParams() {
const posts = getAllPosts()
return posts.map((post) => ({
slug: post.slug,
}))
}
export default async function Post({ params }:{params: Params}) {
const { slug } = await params;
const post: PostFull = await getPostBySlug(slug)
return (
<div className="min-h-screen bg-gradient-to-br from-gray-900 via-gray-800 to-gray-900 text-gray-100 font-sans">
{/* Back button */}
<nav className="container mx-auto px-4 sm:px-6 lg:px-8 py-8">
<a
href="/journal"
className="inline-flex items-center text-gray-400 hover:text-amber-300 transition-colors group"
>
<svg className="w-5 h-5 mr-2 transition-transform duration-300 group-hover:-translate-x-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 19l-7-7m0 0l7-7m-7 7h18" />
</svg>
Back to Journal
</a>
</nav>
{/* Article container */}
<article className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-3xl pb-20">
{/* Article header */}
<header className="mb-12">
<div className="flex justify-between items-start mb-4">
<div>
{post.category && (
<span className="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-amber-500/10 text-amber-300 border border-amber-400/20 mb-4">
{post.category}
</span>
)}
<h1 className="text-4xl md:text-5xl font-bold mb-2">
<span className="bg-clip-text text-transparent bg-gradient-to-r from-amber-300 via-amber-200 to-amber-400">
{post.title}
</span>
</h1>
{post.subtitle && (
<h2 className="text-xl text-gray-300 mt-2">{post.subtitle}</h2>
)}
</div>
</div>
<div className="flex items-center text-gray-400 border-t border-b border-gray-700/50 py-4">
<div className="flex items-center">
<svg className="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
<span>{new Date(post.date).toLocaleDateString('en-UK', {
year: 'numeric',
month: 'long',
day: 'numeric'
})}</span>
</div>
</div>
</header>
{/* Article content - using the client component */}
<MdxContent source={post.content} />
{/* Article footer */}
<footer className="mt-16 pt-8 border-t border-gray-700/50">
<a
href="/journal"
className="inline-flex items-center text-amber-400 hover:text-amber-300 transition-colors group"
>
<svg className="w-5 h-5 mr-2 transition-transform duration-300 group-hover:-translate-x-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 19l-7-7m0 0l7-7m-7 7h18" />
</svg>
Back to Journal
</a>
</footer>
</article>
</div>
)
}
Edit: The full code for 'MdxContent.tsx' is here:
'use client'
import { MDXRemote } from 'next-mdx-remote'
import type { MDXRemoteSerializeResult } from 'next-mdx-remote'
import type { ComponentProps } from 'react'
type MdxContentProps = {
source: MDXRemoteSerializeResult
}
type HeadingProps = ComponentProps<'h1'> & { children?: React.ReactNode }
type ParagraphProps = ComponentProps<'p'> & { children?: React.ReactNode }
type AnchorProps = ComponentProps<'a'> & { children?: React.ReactNode }
type ListProps = ComponentProps<'ul'> & { children?: React.ReactNode }
type ListItemProps = ComponentProps<'li'> & { children?: React.ReactNode }
type CodeProps = ComponentProps<'code'> & { children?: React.ReactNode }
type PreProps = ComponentProps<'pre'> & { children?: React.ReactNode }
type ImageProps = ComponentProps<'img'>
type TableProps = ComponentProps<'table'> & { children?: React.ReactNode }
type TableCellProps = ComponentProps<'th'> & { children?: React.ReactNode }
export function MdxContent({ source }: MdxContentProps) {
return (
<div className="prose prose-invert prose-lg max-w-none">
<MDXRemote {...source} components={{
h1: (props: HeadingProps) => <h1 className="text-3xl font-bold mt-12 mb-6" {...props} />,
h2: (props: HeadingProps) => <h2 className="text-2xl font-bold mt-10 mb-4 relative group" {...props}>
<span className="absolute -left-6 top-1/2 transform -translate-y-1/2 opacity-0 group-hover:opacity-100 transition-opacity">
<span className="text-gray-500 hover:text-amber-400">#</span>
</span>
{props.children}
</h2>,
h3: (props: HeadingProps) => <h3 className="text-xl font-semibold mt-8 mb-3" {...props} />,
p: (props: ParagraphProps) => <p className="my-6 leading-relaxed" {...props} />,
a: (props: AnchorProps) => <a className="text-amber-400 hover:text-amber-300 underline underline-offset-4" {...props} />,
ul: (props: ListProps) => <ul className="list-disc pl-6 space-y-2 my-4" {...props} />,
li: (props: ListItemProps) => <li className="pl-2" {...props} />,
code: (props: CodeProps) => <code className="bg-gray-800/50 px-1.5 py-0.5 rounded text-sm font-mono" {...props} />,
pre: (props: PreProps) => <pre className="bg-gray-800/70 rounded-lg p-4 my-6 overflow-x-auto border border-gray-700" {...props} />,
img: (props: ImageProps) => <img className="rounded-lg my-6 border border-gray-700" {...props} />,
table: (props: TableProps) => <div className="overflow-x-auto"><table className="w-full my-6 border-collapse" {...props} /></div>,
th: (props: TableCellProps) => <th className="text-left p-3 bg-gray-800/50 border border-gray-700" {...props} />,
td: (props: TableCellProps) => <td className="p-3 border border-gray-700" {...props} />,
}} />
</div>
)
}
Edit 2: I've found that the only place that uses 'useState' is with the Navbar where it's used to create mobile navigation.
'use client'
import Link from "next/link"
import { useState } from "react"
export default function Navbar() {
const [menuOpen, setMenuOpen] = useState(false)
return (
<nav className="bg-gray-800/80 backdrop-blur-sm border-b border-gray-700 sticky top-0 z-50 transition-all duration-300 hover:bg-gray-800/90">
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between h-16 items-center">
<div className="flex items-center">
<div className="relative">
<Link href="/" className="text-xl font-bold bg-gradient-to-r from-amber-300 to-amber-500 bg-clip-text text-transparent">
Hotel Toffee
</Link>
</div>
</div>
{/* Desktop Navigation */}
<div className="hidden md:flex space-x-6">
<NavLink href="/" emoji="🏠">Home</NavLink>
<NavLink href="/about" emoji="👤">About</NavLink>
<NavLink href="/journal" emoji="📓">Journal</NavLink>
<NavLink href="/projects" emoji="💡">Projects</NavLink>
</div>
{/* Mobile Menu Button */}
<button
className="md:hidden text-gray-300 hover:text-amber-400 transition-colors"
onClick={() => setMenuOpen(!menuOpen)}
>
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d={menuOpen ? "M6 18L18 6M6 6l12 12" : "M4 6h16M4 12h16M4 18h16"} />
</svg>
</button>
</div>
</div>
{/* Mobile Menu */}
{menuOpen && (
<div className="md:hidden bg-gray-800/95 backdrop-blur-lg py-4 px-4">
<div className="flex flex-col space-y-3">
<MobileNavLink href="/" emoji="🏠">Home</MobileNavLink>
<MobileNavLink href="/about" emoji="👤">About</MobileNavLink>
<MobileNavLink href="/journal" emoji="📓">Journal</MobileNavLink>
<MobileNavLink href="/projects" emoji="💡">Projects</MobileNavLink>
</div>
</div>
)}
</nav>
)
}
// Reusable NavLink component for desktop
function NavLink({ href, emoji, children }) {
return (
<Link href={href} className="px-3 py-2 rounded-lg hover:bg-gray-700/50 transition-all flex items-center group">
<span className="mr-2 group-hover:text-amber-300">{emoji}</span>
<span className="font-medium">{children}</span>
</Link>
)
}
// Reusable NavLink component for mobile
function MobileNavLink({ href, emoji, children }) {
return (
<Link href={href} className="px-3 py-2 rounded-lg hover:bg-gray-700/50 transition-all flex items-center">
<span className="mr-3">{emoji}</span>
<span>{children}</span>
</Link>
)
}
This Navbar.tsx is featured in the Layout.tsx and so is on every page of the blog.
Edit 3: I have tried commenting out the mobile navigation code and all references to 'useState', but it still returns the same error.
I have managed to sort it out. As stated earlier, next-mdx-remote
isn't working with the latest version of Next.js, but there is next-mdx-remote-client
which can be used instead. A few changes need to be made but it isn't a lot overall.
Here is the repository for next-mdx-remote-client
: https://github.com/ipikuka/next-mdx-remote-client