Next.js: How to add hash tag to static assets

Created on 13 Jul 2017  路  10Comments  路  Source: vercel/next.js

Some of my components refer the assets files in a css way:

import styled from 'styled-components';
const Logo = styled.div`
     background-image:  url('/static/logo.png'),
`

I want to use CDN for all the assets in /statics folder, it is possible to give a version tag( like BUILD_ID) to these files in production?

No clue, anyone has a advice?

Most helpful comment

File: ./build-static.js

const fs = require('fs')
const path = require('path')
const execSync = require('child_process').execSync
const revHash = require('rev-hash')

const srcDir = 'static'
const distDir = 'buildstatic'
const hashData = {}

execSync(`rm -r -f ${distDir}`)

const buildStatic = (src, dist) => {
  fs.access(dist, (err) => {
    if (err) {
      fs.mkdirSync(dist)
    }

    initBuild(src, dist)
  })
}

const initBuild = (src, dist) => {
  const paths = fs.readdirSync(src)

  paths.forEach((_path) => {
    const subSrc = `${src}/${_path}`
    const subDist = `${dist}/${_path}`
    const stat = fs.statSync(subSrc)

    if (stat.isFile()) {
      const fileData = fs.readFileSync(subSrc)
      const pathObj = path.parse(subSrc)
      const hash = revHash(fileData)
      const hashDir = pathObj.dir.split(`${srcDir}/`)[1] || ''
      const hashKey = `${hashDir}/${pathObj.name}${pathObj.ext}`
      const filePath = `${path.parse(subDist).dir}/${pathObj.name}.${hash}${pathObj.ext}`

      if (pathObj.ext) {
        hashData[hashKey] = hash
        fs.writeFileSync(filePath, fileData)
      }
    } else if (stat.isDirectory()) {
      buildStatic(subSrc, subDist)
    }
  })

  fs.writeFileSync('static-hash.json', JSON.stringify(hashData))
}

buildStatic(srcDir, distDir)

run: node build-static.js

File: ./demo.js

import staticHash from 'static-hash'

const isPro = true

const staticRequire = (path) => {
  const hash = staticHash[path]
  const pathSplit = path.split('.')
  const fileName = pathSplit[0]
  const fileExt = pathSplit[1]
  const filePath = !isPro ? path : `${fileName}.${hash}.${fileExt}`

  let staticDir = '/static/'

  if (isPro) {
    staticDir = 'https://mycdn.com/static/'
  }

  return `${staticDir}${filePath}`
}

console.log(staticRequire('images/test.png'))

// <img src={staticRequire('images/test.png')} alt="" />

All 10 comments

Same issue here.
We tried using webpack file loader but seems not working for the current version of next.js.

We also encountered this problem, if the use of file-loader, you can generate a new image, but the node will be reported when compiled:

Error: Cannot find module '../ static / test.png'

So temporarily through githooks to deal with images to modify the problem, allowing add new image, but not allowed to submit modify the image:

.git/hooks/pre-commit

#!/bin/sh
STAGED_IMAGES=$(git diff --cached --name-only --diff-filter=M | grep -E ".(jpg|jpeg|png|gif)$")
if [[ $STAGED_IMAGES ]]; then
  echo $STAGED_IMAGES
  exit 1
fi

Actually this is pretty easy using the with-universal-configuration example.

File: ./env-config.js

const { version } = require('./package.json')

module.exports = {
  'process.env.VERSION': version
}

File: ./pages/index.js

export default () => (
  <div>
    Build-Version: { process.env.VERSION }
  </div>
)

I've created a fork to demonstrate this: HaNdTriX/next.js/tree/with-build-id/examples/with-universal-configuration

If you would like to add this to the examples I can create a standalone example and create a PR.

If this example solves your issue please close it.

Thanks @HaNdTriX 鉂わ笍

@HaNdTriX @timneutkens
Thanks for the suggestion. It seems good for some issues here but not all of them.
For our project, we are working for images hash tags with next.js. It will be better to have the hash for every single image in order to make the browser cache the right one without extra clear work.
So the build version is only working for the release build code but not for the assets stuff as images and css files.

Thanks @HaNdTriX, but I have the same question here, what I want is the production code build all the static files with hash names, such as static/avatar.png -> static/avatar.1898173978.png.

The images may change sometime, we don't to refresh CDN on these files frequently, hash name can avoid this problem.

Could you give me some suggestions?

I thought about something like this:

<style jsx global>{`
  body {
    background-image: url(static/some-image.png?version=${process.env.VERSION});
  }
`}</style>

What you use as an hash is up to you. You can create it yourself in the ./env-config.js.

File: ./build-static.js

const fs = require('fs')
const path = require('path')
const execSync = require('child_process').execSync
const revHash = require('rev-hash')

const srcDir = 'static'
const distDir = 'buildstatic'
const hashData = {}

execSync(`rm -r -f ${distDir}`)

const buildStatic = (src, dist) => {
  fs.access(dist, (err) => {
    if (err) {
      fs.mkdirSync(dist)
    }

    initBuild(src, dist)
  })
}

const initBuild = (src, dist) => {
  const paths = fs.readdirSync(src)

  paths.forEach((_path) => {
    const subSrc = `${src}/${_path}`
    const subDist = `${dist}/${_path}`
    const stat = fs.statSync(subSrc)

    if (stat.isFile()) {
      const fileData = fs.readFileSync(subSrc)
      const pathObj = path.parse(subSrc)
      const hash = revHash(fileData)
      const hashDir = pathObj.dir.split(`${srcDir}/`)[1] || ''
      const hashKey = `${hashDir}/${pathObj.name}${pathObj.ext}`
      const filePath = `${path.parse(subDist).dir}/${pathObj.name}.${hash}${pathObj.ext}`

      if (pathObj.ext) {
        hashData[hashKey] = hash
        fs.writeFileSync(filePath, fileData)
      }
    } else if (stat.isDirectory()) {
      buildStatic(subSrc, subDist)
    }
  })

  fs.writeFileSync('static-hash.json', JSON.stringify(hashData))
}

buildStatic(srcDir, distDir)

run: node build-static.js

File: ./demo.js

import staticHash from 'static-hash'

const isPro = true

const staticRequire = (path) => {
  const hash = staticHash[path]
  const pathSplit = path.split('.')
  const fileName = pathSplit[0]
  const fileExt = pathSplit[1]
  const filePath = !isPro ? path : `${fileName}.${hash}.${fileExt}`

  let staticDir = '/static/'

  if (isPro) {
    staticDir = 'https://mycdn.com/static/'
  }

  return `${staticDir}${filePath}`
}

console.log(staticRequire('images/test.png'))

// <img src={staticRequire('images/test.png')} alt="" />

Thanks @taichenglu .

Similar solution here, I decide to add a suffix to static assets (e.g. /static/app_icon.png?v=1).

import qs from 'querystring';

export default (url) => {
    if (process.env.BUILD_VERSION) {
        const [urlPath, urlQueryString = ''] = url.split('?');
        const queryString = qs.parse(urlQueryString);
        queryString.v = process.env.BUILD_VERSION;
        return `${urlPath}?${qs.stringify(queryString)}`;
    }
    return url;
}

We use Jenkins to deploy code, the BUILD_VERSION is assigned to Jenkins Job id.

@monopieces
If you do so, each release will update all static files, is not conducive to the client cache.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

dunika picture dunika  路  58Comments

timneutkens picture timneutkens  路  250Comments

timneutkens picture timneutkens  路  72Comments

nickredmark picture nickredmark  路  60Comments

baldurh picture baldurh  路  74Comments