3

I am implementing nextjs dynamic route for my static documentation page. It works perfectly in local, and it compiled successfully but it doesn't work with production URL.

I think my current code getStaticPaths url handling is something strange and producing strange %2F value in my URI as below. For example, the correct path has to be example.com/docs/en-integrations-hello-test.

enter image description here

In my [...slug].tsx

export const getStaticPaths = async () => {
    // get current path
    const myProdPath = path.join(process.cwd(), 'src/')

    // grab all markdown files
    const docPaths = await glob(path.join(myProdPath, '**/*.md'))


    // For each filename, the slug is the filename without the .md extension
    const paths = await docPaths.map((docPath) => {
        const slug = docPath
            .replace(myProdPath, '')
            .replace('.md', '')

        console.log('this is slug:   ' + slug) < prints out corrently without %2F

        return { params: { slug: [slug] } }
    })

    // Return the possible paths
    return { paths, fallback: true }
}

Is there any solution to fix this?

husky
  • 831
  • 3
  • 10
  • 24

1 Answers1

0

I encountered a similar issue in NextJS 13 using the App router in a catch-all route segment (folder named [...slug])and the generateStaticParams function, which has a return signature similar to what you're showing here for getStaticPaths. I was trying to dynamically generate blog post slug paths based on a specific folder's contents, including subdirectories. Here is what I discovered and how I solved it.

The Problem

The next build and next dev commands handle processing static parameters differently in some meaningful way. next dev returns the expected path including the slashes when getting the filenames using globSync:

export async function generateStaticParams() {
  const filenames = globSync(
      [postsDirectory + '/**/*.md', postsDirectory + '/**/*.mdx'],
      { absolute: false, cwd: postsDirectory },
    )
  const markdownRegex = /\.md(x)?$/
  return filenames.map((filename) => ({
      slug: [filename.replace(markdownRegex, '')],
    }))
}

generateStaticParams in dev returns an array of parameters with slugs like these:

[
  { slug: [ 'article2' ] },
  { slug: [ 'article1' ] },
  { slug: [ '2023/newest-article' ] },
  { slug: [ '2023/directory/some-other-article' ] }
]

which are accessed via code like this:

export default async function ArticleBySlug({
  params,
}: {
  params: { slug: string[] }
}) {
  const filePath = path.join(postsDirectory, params.slug[0])

  // Do something with filePath to get article contents and build page...

However, when this code ran in next build, the resulting array looks more like this:

[
  { slug: [ 'article2' ] },
  { slug: [ 'article1' ] },
  { slug: [ '2023%2Fnewest-article' ] },
  { slug: [ '2023%2Fdirectory%2Fsome-other-article' ] }
]

Obviously, these filenames can't be found, and the build process crashes.

The Solution

The answer was to make use of the fact that the slug parameter is expecting an array. Instead of returning an array of one string with slashes (filename.replace(markdownRegex, '')), we can return an actual array of route segments. The code now looks like this:

export async function generateStaticParams() {
  const filenames = globSync(
      [postsDirectory + '/**/*.md', postsDirectory + '/**/*.mdx'],
      { absolute: false, cwd: postsDirectory },
    )
  const markdownRegex = /\.md(x)?$/
  return filenames.map((filename) => ({
      slug: filename.replace(markdownRegex, '').split(path.sep)
    }))
}

Now instead of returning an array containing one path string from which we remove the file extension, we are returning the results of using string.split on that path, splitting on path.sep which will be the operating system appropriate separator. This results in an array of segments in the slug property for each parameter, such as:

[
  { slug: [ 'article2' ] },
  { slug: [ 'article1' ] },
  { slug: [ '2023', 'newest-article' ] },
  { slug: [ '2023', 'directory', 'some-other-article' ] }
]

Then, instead of joining the string directly, we can use the spread operator to expand the array into path.join:

export default async function ArticleBySlug({
  params,
}: {
  params: { slug: string[] }
}) {
  const filePath = path.join(postsDirectory, ...params.slug)

  // Do something with filePath to get article contents and build page...

This pattern works equally well in next build and next dev (using NextJS 13 and the app router). I assume if you are returning an array of strings as the slug parameter, it should work in the pages router with getStaticPaths as well.

coppereyecat
  • 161
  • 2
  • 13