Dynamic JSON imports inside getStaticProps
Answered
Perro de Presa Canario posted this in #help-forum
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
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:
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`)4 Replies
Perro de Presa CanarioOP
I've been trying to use this officially documented way of reading files with
Locally it works fine.
https://nextjs.org/docs/pages/api-reference/functions/get-static-props#reading-files-use-processcwd
My code (simplified):
Error the code produces:
fs but also it doesn't work in Vercel. Build goes through nicely, but then on runtime the functions just crashLocally 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.ExitErrorPerro de Presa CanarioOP
Interesting thing here is that during build phase the logs show that the fs is able to read the correct files:
But then when the function runs outside the build, it sees the files correctly but fails still:
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 workingPerro 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:
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