Next.js API routes: How to read files from directory (compatible with vercel)
Lately I was having a use case of reading images from an in-project-directory in Next.js. I couldn’t use getStaticProps (because it was conflicting with getInitialProps, which I had to use for another library), so I had to use an api route in order to read the images (or rather their paths) from the directory.
The images are lying in /public/img
and its amount is variable, which is why we want to dynamically read (and fetch) all the images in there (not manually adapt our code every time a new image is added):
Page Code
Let’s first intialize our code for our page:
// pages/index.js
import useSWR from 'swr';
export default function Home() {
const fetcher = (url) => fetch(url).then((res) => res.json());
const { data } = useSWR('/api/readfiles', fetcher);
return (
<div>
...
<main>
<h1>Images read from API route: </h1>
{!data && "Loading..."}
{data && data.map(imgPath => <img src={imgPath} alt=""/>)}
</main>
...
</div>
)
}
Okay, so we want to use swr in order to fetch the images from our api route. As soon as the data is ready, we want to map over the returned data. The data is expected to be an array of image paths.
API Route: The wrong approach
The approach, as proposed here, works to display the images correctly:
// pages/api/readfiles.js
import fs from 'fs'
import path from 'path'
import getConfig from 'next/config'
export default (req, res) => {
const { serverRuntimeConfig } = getConfig()
const dirRelativeToPublicFolder = 'img'
const dir = path.join(serverRuntimeConfig.PROJECT_ROOT, './public', dirRelativeToPublicFolder);
const filenames = fs.readdirSync(dir);
const images = filenames.map(name => path.join('/', dirRelativeToPublicFolder, name))
res.statusCode = 200
res.json(images);
}
// next.config.js
module.exports = {
serverRuntimeConfig: {
PROJECT_ROOT: __dirname
}
}
As we can see when running npm run dev
, the images are displayed in the browser:
But after deploying our project to vercel, the following is shown:
When inspecting the logs on the vercel dashboard, it gets clear, that our images folder is not found (because it’s not included in the serverless lambda):
ERROR: ENOENT: No such file or directory, scandir '/vercel/workpath0/public/img'
So let’s refactor our api route to be compatible with vercel!
API Route: The right approach
First of all, we can delete our next.config.js
or at least remove the PROJECT_ROOT
entry.
Lastly, let’s update our API route to have the following code:
import fs from 'fs'
import path from 'path'
export default (req, res) => {
const dirRelativeToPublicFolder = 'img'
const dir = path.resolve('./public', dirRelativeToPublicFolder);
const filenames = fs.readdirSync(dir);
const images = filenames.map(name => path.join('/', dirRelativeToPublicFolder, name))
res.statusCode = 200
res.json(images);
}
The important part is path.resolve(...)
, which instructs vercel to include the scanned path in the serverless lambda. The shown code works to read images (or other files from your fs) both locally and remotely on vercel!
Conclusion
Use the above code snippet to read files in your api route in a way that vercel doesn’t complain.
If you want to take a look at the complete project, check out the GitHub repository.