Search code examples
javascriptnode.jsnext.jsfs

Encoding static imagery from public folder using getStaticProps in Next.js


Next.js lays out a pretty comprehensive way to get imagery from the /public/ folder (where the app has you store static assets). The pattern is to use fs from Node and do the fetch in getStaticProps.

My attempt:

export async function getStaticProps({ params, preview = false, previewData }) {
  const cityData = dataFiltered.find( city => city.citySlug === params.slug )
  const cityMapImagePath = path.join(process.cwd(), `/public/static-maps/${cityData.imgPath}`)
  const cityMapImageRes = await fs.readFile(cityMapImagePath)
  const cityMapImageProcessed = JSON.stringify(cityMapImageRes)

  return {
    props: {
      preview,
      cityData: cityData,
      cityMapImage: cityMapImageProcessed
    },
    revalidate: 60,
  };
}

This code works, but it returns a pretty weird response when I reference that object in my component:

<img src="{ "type":"Buffer", "data":[255,216,255,224,0,6,75,56,86,87,...] } />

My error has something to do with how I'm processing what fs gives me back. Do I need to encode my jpeg into base64 to get Next to use it? This answer suggests stringifying and then parsing (didn't work for me). Or maybe I need a full blown endpoint to do this? Next isn't very clear on how to get imagery from getStaticProps into the component above it - perhaps you know how?


Solution

  • What I ended up doing for the fetch in getStaticProps:

    export async function getStaticProps({ params, preview = false, previewData }) {
      const cityData = dataFiltered.find( city => city.citySlug === params.slug )
      const cityMapImagePath = path.join(process.cwd(), `/public/static-maps/${cityData.imgPath}`)
      let cityMapImageRes, cityMapImageProcessed
    
      try {
        cityMapImageRes = await fs.readFile(cityMapImagePath)
        cityMapImageProcessed = Buffer.from(cityMapImageRes).toString('base64')
      } catch {
        cityMapImageProcessed = null
      }
    
      return {
        props: {
          preview,
          cityData: cityData,
          cityMapImage: cityMapImageProcessed
        },
        revalidate: 60,
      };
    }
    

    Also make sure up in the component, you are properly encoding your image source as base64 with the data:image/png;base64, prefix. This was a silly mistake that cost me an hour of debugging:

    <img src={`data:image/png;base64,${cityMapImage}`} alt='Alt text here' />
    

    Finally, also note that Next.js when used with Vercel will impose a hard, 50MB cap (compressed) on the serverless function used to process your page file request. If you're doing a [slug].js template, all the assets for EVERY page generated will count towards that cap. You'll hit it pretty fast on deploy, so double check yourself.

    Update 2024-02: serverless bundle size can now go up to 250MB