reactjsvercelreact-typescriptnext.js15

Error occurred prerendering page TypeError: Cannot read properties of null


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.


Solution

  • 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