10

Hope you all are having a good time. I am working on a simple NextJs application where I want to have multiple subdomains in it. I am deploying the application on vercel.

What my application does is it have a simple textarea where you write MDX, you click the Publish button and it will save that MDX into firebase firestore. Below the textarea it shows the list of all the pages that have been published before.

The application renders the list of all the pages like with name of the page which is randomly generated as the subdomain while the actual domain comes later like the following.

a-mdx-page.mydomain.app

When I open that URL it will fetch the page MDX from firestore and uses next-mdx-remote package to serialize and render the MDX. The reason for using the next-mdx-remote package is that we can add react components in MDX and it can render as normal react components. I already have a custom domain because you cannot have a subdomain on top of a free subdomain in vercel free deployment.

All works fine on localhost and everything is as it should be but the problem is when I deploy the code on Vercel and navigate to subdomain it shows ERROR 500 on the website and shows the following error in the logs.

[GET] /   21:21:03:30

2021-10-24T16:21:04.018Z    8e52d5da-ff1f-4840-a09b-199233834a5d    ERROR   Error: The package "esbuild-linux-64" could not be found, and is needed by esbuild.
If you are installing esbuild with npm, make sure that you don't specify the
"--no-optional" flag. The "optionalDependencies" package.json feature is used
by esbuild to install the correct binary executable for your current platform.
    at generateBinPath (/var/task/node_modules/esbuild/lib/main.js:1643:15)
    at esbuildCommandAndArgs (/var/task/node_modules/esbuild/lib/main.js:1699:11)
    at ensureServiceIsRunning (/var/task/node_modules/esbuild/lib/main.js:1856:25)
    at Object.transform (/var/task/node_modules/esbuild/lib/main.js:1751:37)
    at serialize (/var/task/node_modules/next-mdx-remote/dist/serialize.js:287:43)
    at async getServerSideProps (/var/task/.next/server/pages/index.js:261:25)
    at async Object.renderToHTML (/var/task/node_modules/next/dist/server/render.js:428:24)
    at async doRender (/var/task/node_modules/next/dist/server/next-server.js:1144:38)
    at async /var/task/node_modules/next/dist/server/next-server.js:1236:28
    at async /var/task/node_modules/next/dist/server/response-cache.js:64:36 {
  page: '/'
}
RequestId: 8e52d5da-ff1f-4840-a09b-199233834a5d Error: Runtime exited with error: exit status 1
Runtime.ExitError

From what I understand that the next-mdx-remote serialize function uses esbuild in it and when I deploy the application on vercel npm just doesn't downloads the platform specific package of it but may be I am wrong.

I have tried to search the solution for this but there is not any answers that helped me.

Following is all the code that the application uses.

import { useState } from "react"
import { collection, doc, getDoc, getDocs, setDoc } from "firebase/firestore"
import matter from "gray-matter"
import { MDXRemote } from "next-mdx-remote"
import { serialize } from "next-mdx-remote/serialize"
import {
  uniqueNamesGenerator,
  adjectives,
  colors,
  animals,
} from "unique-names-generator"

import { db } from "../utils/fire-client"
import Layout from "../components/Layout"
import { HOSTNAME } from "../config"
import MDXComponents from "../components/mdx"

export default function Index({ posts, isPage = false, mdxSource }) {
  const [mdxCode, setMdxCode] = useState("# THIS IS MDX")
  const [message, setMessage] = useState("")

  const addPageToCollection = async (name, content) => {
    const pagesCollection = collection(db, "pages")
    await setDoc(doc(pagesCollection, name), {
      name,
      content,
    })
  }

  function publishPage() {
    const randomName = uniqueNamesGenerator({
      dictionaries: [adjectives, colors, animals],
    })

    addPageToCollection(randomName, mdxCode)
    setMessage(
      "New Page Added: " + randomName + "\nReload page To see it in the list"
    )

    setTimeout(() => {
      setMessage("")
    }, 5000)
  }

  return (
    <Layout>
      {isPage ? (
        <>
          <header>
            <nav>
              <a href={"http://" + HOSTNAME}>
                <a> Go back home</a>
              </a>
            </nav>
          </header>
          <main>
            <MDXRemote {...mdxSource} components={MDXComponents} />
          </main>
        </>
      ) : (
        <>
          <h1>Home Page</h1>

          <textarea
            name="mdxCode"
            id="mdxCode"
            value={mdxCode}
            onChange={(e) => setMdxCode(e.target.value)}
            className="w-full h-1/2 border-2 border-gray-400 p-2"
          />

          <button className="btn btn-primary" onClick={publishPage}>
            Publish
          </button>

          <div>{message}</div>

          <ul>
            <div className="mt-4 font-bold">Pages List</div>
            {posts.map((post) => (
              <li key={post.name}>
                <a href={`http://${post.name}.${HOSTNAME}`}>{post.name}</a>
              </li>
            ))}
          </ul>
        </>
      )}
    </Layout>
  )
}

export async function getServerSideProps({ req, res }) {
  const host = req.headers.host.split(".")

  if (host[0] !== HOSTNAME.split(".")[0] && host[0] !== "www") {
    const docRef = doc(db, "pages", host[0])
    const docSnap = await getDoc(docRef)

    if (docSnap.exists()) {
      const { content, data } = matter(docSnap.data().content)
      const mdxSource = await serialize(content, {
        // Optionally pass remark/rehype plugins
        mdxOptions: {
          remarkPlugins: [],
          rehypePlugins: [],
        },
        scope: data,
      })

      if (mdxSource) {
        return {
          props: {
            isPage: true,
            mdxSource,
          },
        }
      }
    } else {
      return {
        props: {
          redirect: {
            destination: "/",
          },
        },
      }
    }
  }

  const pagesCollection = collection(db, "pages")
  const pagesSnapshot = await getDocs(pagesCollection)
  const pagesList = pagesSnapshot.docs.map((doc) => doc.data())

  if (pagesList.length > 0) {
    return {
      props: {
        posts: pagesList,
      },
    }
  }

  return { props: { posts } }
}
juliomalves
  • 42,130
  • 20
  • 150
  • 146
zainsci
  • 209
  • 2
  • 8

2 Answers2

5

Update esbuild to 0.13.4 or higher

npm i -D esbuild@0.13.4

See: https://github.com/evanw/esbuild/releases/tag/v0.13.4

Jose Manosalvas
  • 161
  • 1
  • 4
0

If building with Docker this could be due to an incomplete .dockerignore file that doesn't ignore all your node_modules folder.

CorayThan
  • 17,174
  • 28
  • 113
  • 161