Next.js Discord

Discord Forum

Dynamic JSON imports inside getStaticProps

Answered
Perro de Presa Canario posted this in #help-forum
Open in Discord
Perro de Presa CanarioOP
We have been using dynamic loading for i18n JSON translations in production for three years now. The issue is that now when we updated from Next 13.2 ,3.4 the dynamic loading broke: after the update the page just crashes on Vercel with an error message of not.

Does anyone have any tips on how we could import just the needed translation files from static JSON files inside the getStaticProps? Or is there something else that I'm missing here?

Here is a bit simplified version of how we used to load the translations, but which is not working anymore
export async function getStaticProps(context) {
  const { locale } = context
  const { default: translations } = await import(`locales/${locale}.json`)

  return {
    props: { lng: locale, lngDict: { translations } }
  }
}
Answered by Perro de Presa Canario
I found the issue. Vercel seems to "optimise" the build so that it guesses which files are needed with the functions. Since the import is dynamic, it can't figure this out.

I fixed it with this way:
  // 'scope' is part of the folders for translations
  const directory = path.join(process.cwd(), 'locales', scope)
  const filenames = await fs.readdir(directory)

  // While it IS stupid to include all the files and choose the only the one that
  // matches our need, it's the only way to make it work with Vercel. If we include
  // only one file dynamically, Vercel "optimises" the build and doesn't include any
  // of the needed files with the lambda functions.
  const files = await Promise.all(
    filenames.map(async filename => {
      const filePath = path.join(directory, filename)
      const fileContents = await fs.readFile(filePath, 'utf8')
      const file = JSON.parse(fileContents)
      return { filename, file }
    })
  )

  // Return only the needed file
  const { file } = files.find(_file => _file.filename === `${locale}.json`)
View full answer

4 Replies

Perro de Presa CanarioOP
I've been trying to use this officially documented way of reading files with fs but also it doesn't work in Vercel. Build goes through nicely, but then on runtime the functions just crash

Locally it works fine.

https://nextjs.org/docs/pages/api-reference/functions/get-static-props#reading-files-use-processcwd

My code (simplified):
  const filePath = path.join(process.cwd(), `/locales/translations/${locale}.json`)
  const fileData = await fs.readFile(filePath, 'utf8')
  const file = JSON.parse(fileData)


Error the code produces:
[Error: ENOENT: no such file or directory, open '/var/task/locales/translations/en.json'] {
  errno: -2,
  syscall: 'open',
  path: '/var/task/locales/translations/en.json'
}
RequestId: 70be5ac8-5ff1-4b96-b9a4-1d767390d337 Error: Runtime exited with error: exit status 1
Runtime.ExitError
Perro de Presa CanarioOP
Interesting thing here is that during build phase the logs show that the fs is able to read the correct files:
process.cwd(): /vercel/path0 

fs.readdir(path.join(process.cwd(), '/locales/translations')):
[
  'ar.json',    'bn.json',    'de.json',
  'en.json',    'es.json',    'fi.json',
  'fr.json',    'hi.json',    'id.json',
  'it.json',    'ja.json',    'km.json',
  'lo.json',    'ms-MY.json', 'nl.json',
  'pa.json',    'pl.json',    'pt.json',
  'ru.json',    'sv.json',    'te.json',
  'th.json',    'tl.json',    'tr.json',
  'uk.json',    'ur.json',    'vi.json',
  'zh-CN.json'
]


But then when the function runs outside the build, it sees the files correctly but fails still:
process.cwd(): /var/task

fs.readdir(path.join(process.cwd(), '/locales/translations')):
[
  'ar.json',    'bn.json',    'de.json',
  'en.json',    'es.json',    'fi.json',
  'fr.json',    'hi.json',    'id.json',
  'it.json',    'ja.json',    'km.json',
  'lo.json',    'ms-MY.json', 'nl.json',
  'pa.json',    'pl.json',    'pt.json',
  'ru.json',    'sv.json',    'te.json',
  'th.json',    'tl.json',    'tr.json',
  'uk.json',    'ur.json',    'vi.json',
  'zh-CN.json'
]

[Error: ENOENT: no such file or directory, open '/var/task/locales/translations/en.json'] {
  errno: -2,
  syscall: 'open',
  path: '/var/task/locales/translations/en.json'
}
[Error: ENOENT: no such file or directory, open '/var/task/locales/translations/en.json'] {
  errno: -2,
  syscall: 'open',
  path: '/var/task/locales/translations/en.json',
  page: '/en/solution/brand-monitoring-and-social-listening'
}
Perro de Presa CanarioOP
The imports also work in all the files which are not dynamically rendered based on URL params, like pages/index.js and pages/pricing/index.js, but then paths like pages/solution/[id]/index.js are not working
Perro de Presa CanarioOP
I found the issue. Vercel seems to "optimise" the build so that it guesses which files are needed with the functions. Since the import is dynamic, it can't figure this out.

I fixed it with this way:
  // 'scope' is part of the folders for translations
  const directory = path.join(process.cwd(), 'locales', scope)
  const filenames = await fs.readdir(directory)

  // While it IS stupid to include all the files and choose the only the one that
  // matches our need, it's the only way to make it work with Vercel. If we include
  // only one file dynamically, Vercel "optimises" the build and doesn't include any
  // of the needed files with the lambda functions.
  const files = await Promise.all(
    filenames.map(async filename => {
      const filePath = path.join(directory, filename)
      const fileContents = await fs.readFile(filePath, 'utf8')
      const file = JSON.parse(fileContents)
      return { filename, file }
    })
  )

  // Return only the needed file
  const { file } = files.find(_file => _file.filename === `${locale}.json`)
Answer