Search code examples
javascriptimagewebpacksasssass-loader

Images not displaying on production build, only in dev (webpack/sass-loader)


When running my app on webpack dev server, all my image files are working whether as img tags in my index.html or background-image: url()..

Running my project though in production build, I am getting file reference errors that they cannot be found.

GET file:///img/featured.png net::ERR_FILE_NOT_FOUND
GET file:///img/header-img1-1.jpg net::ERR_FILE_NOT_FOUND

I added the copy webpack plugin as I thought this would move all images from my src/img folder to my img folder inside dist.

Should I be using the contentBase option for webpack-dev-server? Or is the copy-webpack-plugin not getting the correct reference? Super confused

Project tree:

- webpack.config.js
- package.json
- .babelrc
- src
  - js
    - index.js
    - ...
  - img
    - ALL IMAGES LOCATED HERE
  - scss
    - layout
      - landing.scss
      - brands.scss 
    - base
  - index.html

inside landing.scss i have used

background: url('~/img/img-title.png')

same in other files like brands.scss

background: url('~/img/img-title.png')

Which has all worked fine, and I think I've confused myself with how images are referenced with webpack/sass loader, and can't seem to work out how to get the image paths to work for both dev/production, i can only seem to get one working at a time.

production tree:

- dist
  - css
  - img
  - js
  - index.html

webpack.config.js:

const path = require('path');
const autoprefixer = require('autoprefixer');

const ExtractTextPlugin = require('extract-text-webpack-plugin');
const extractPlugin = new ExtractTextPlugin({
  filename: 'css/main.css'
});

const HtmlWebpackPlugin = require('html-webpack-plugin');
const ScriptExtPlugin = require('script-ext-html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');

module.exports = (env) => {
  const isProduction = env.production === true

  return {
	entry: './src/js/index.js',
	output: {
		path: path.resolve(__dirname, 'dist'),
		filename: 'js/bundle.js', 
	},
	module: {
	  rules: [
		{
	      test: /\.js$/,
	      include: path.resolve(__dirname, 'src'),
		  use: 'babel-loader' 
		},
		{
		  test: /\.css$|\.scss$/,
		  include: path.resolve(__dirname, 'src'),
		  use: extractPlugin.extract({  
			fallback: "style-loader",
			use: [
			  { loader: 'css-loader', options: { importLoaders: 2, sourceMap: true }},
			  { loader: 'postcss-loader', options: { sourceMap: true, plugins: () => [autoprefixer] }},
			  { loader: 'sass-loader', options: { sourceMap: true }},
			],
		  }) 
		},
		{
		  test: /\.html$/,
		  use: ['html-loader']
		},
		{
		  test: /\.(jpe?g|png|gif)$/,
		  use: [
			{
		      loader: 'url-loader',
			  options: {
				limit: 1000,
				name: 'img/[name].[ext]',
			  }
			}
		  ]
		}
	  ]
	},
	plugins: [
	  extractPlugin,
	  new HtmlWebpackPlugin({ 
		filename: 'index.html',
		inject: true,
		template: './src/index.html'
	  }),
	  new ScriptExtPlugin({
	    defaultAttribute: 'async'
	  }),
	  new CleanWebpackPlugin(['dist']),
	  new CopyWebpackPlugin([
	    {from:'src/img',to:'img'} 
	  ]), 
	]
  }
}


Solution

  • I think you're using different folder structure on production than on local, i.e. on local, it's just http://localhost:PORT/app, but on prod, it must be similar to http://produrl/Some_Folder/app

    Now coming to actual issue - It's your CSS loader.

    By default css-loader, has url=true, causing all URLs to be mapped relative to root. Hence this works for you -

    background: url('~/img/img-title.png')
    

    but this doesn't

    background: url('../../img/img-title.png')
    

    Just set url=false, and you'll be able to provide relative URL and it'll load for all enviorments correctly.