1

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.

4
  • github.com/hashicorp/next-mdx-remote/issues/472 Commented Jun 13 at 2:17
  • I’m voting to close this question because it's an error in a 3rd party library. Raise an issue with the maintainer Commented Jun 13 at 2:19
  • 1
    Can you share the file @/app/components/MdxContent ? I want to help you. Commented Jun 13 at 14:07
  • 1
    Sorry for the wait, I've now added the code for the 'MdxContent.tsx' file. Commented Jun 17 at 8:24

2 Answers 2

1

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

Sign up to request clarification or add additional context in comments.

Comments

-1

Looks like you are using useState inside MDXRemote, but page.tsx is an async server component. React hooks like useState can't run in server components.

Try wrapping MDXRemote in a "use client" wrapper component

'use client'
import { MDXRemote } from 'next-mdx-remote'
export function MdxContent(props) {
  return <MDXRemote {...props} />
}

And then import and use it like this

import { MdxContent } from '@/app/components/MdxContentClient'

This should solve the useState error at build time.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.