61

My webpack config specifies that fonts should be loaded using url-loader, and when I try to view the page using Chrome I get the following error:

OTS parsing error: invalid version tag
Failed to decode downloaded font: [My local URL]

The relevant parts of my config look like this:

{
  module: {
    loaders: [
      // ...
      {
        test: /\.scss$/,
        loaders: ['style', 'css?sourceMap', 'autoprefixer', 'sass?sourceMap'],
      },
      {
        test: /images\/.*\.(png|jpg|svg|gif)$/,
        loader: 'url-loader?limit=10000&name="[name]-[hash].[ext]"',
      },
      {
        test: /fonts\/.*\.(woff|woff2|eot|ttf|svg)$/,
        loader: 'file-loader?name="[name]-[hash].[ext]"',
      }
    ],
  },
}

It doesn't happen in Safari, and I haven't tried Firefox.

In development I'm serving files through webpack-dev-server, in production they're written to disk and copied to S3; in both cases I get the same behaviour in Chrome.

This also happens to larger images (greater than the 10kB limit in the image loader config).

Will Madden
  • 6,477
  • 5
  • 28
  • 20

14 Answers14

142

TL;DR Use absolute paths to your assets (including your complete hostname) by setting your output.publicPath to e.g. "http://example.com/assets/".

The problem

The problem is the way that URLs are resolved by Chrome when they're parsed from a dynamically loaded CSS blob.

When you load the page, the browser loads your Webpack bundle entry JavaScript file, which (when you're using the style-loader) also contains a Base64 encoded copy of your CSS, which gets loaded into the page.

Screenshot of embedded CSS in Chrome DevTools This is what it looks like in Chrome DevTools

That's fine for all the images or fonts which are encoded into the CSS as data URIs (i.e. the content of the file is embedded in the CSS), but for assets referenced by URL, the browser has to find and fetch the file.

Now by default the file-loader (which url-loader delegates to for large files) will use relative URLs to reference assets - and that's the problem!

Relative URLs generated by Webpack These are the URLs generated by file-loader by default - relative URLs

When you use relative URLs, Chrome will resolve them relative to the containing CSS file. Ordinarily that's fine, but in this case the containing file is at blob://... and any relative URLs are referenced the same way. The end result is that Chrome attempts to load them from the parent HTML file, and ends up trying to parse the HTML file as the content of the font, which obviously won't work.

The Solution

Force the file-loader to use absolute paths including the protocol ("http" or "https").

Change your webpack config to include something equivalent to:

{
  output: {
    publicPath: "http://localhost:8080/", // Development Server
    // publicPath: "http://example.com/", // Production Server
  }
}

Now the URLs that it generates will look like this:

enter image description here Absolute URLs!

These URLs will be correctly parsed by Chrome and every other browser.

Using extract-text-webpack-plugin

It's worth noting that if you're extracting your CSS to a separate file, you won't have this problem because your CSS will be in a proper file and URLs will be correctly resolved.

Idan Gozlan
  • 3,173
  • 3
  • 30
  • 47
Will Madden
  • 6,477
  • 5
  • 28
  • 20
  • 1
    Wow, just had this issue the past week. What happens when you have many deployments or development environments, isn't it tedious to change IP's manually? – Ben Orozco Dec 07 '15 at 17:56
  • 2
    @benoror it's not a problem for deployments that don't use `style-loader` because the CSS will be served from a separate CSS file and Chrome will be able to resolve asset URLs correctly. So you should be fine for e.g. staging, test, QA environments where you deploy your app somewhere and extract CSS. But if you have many development environments it could be a problem. In our team at least we just run the dev server once, locally, and work from that. It's not ideal, and I had to notify my teammates about the change to config, but it doesn't really affect us. – Will Madden Dec 08 '15 at 10:25
  • Cool, good to know. Nevertheless I think it can be done with some script automation, although not ideal. Thanks for the info, cheers! – Ben Orozco Dec 09 '15 at 17:07
  • 1
    BTW, just saw a video which might be related to this particular issue: https://youtu.be/MzVFrIAwwS8?t=870 – Ben Orozco Dec 09 '15 at 17:44
  • Unfortunately, this breaks the ability to use localtunnel. – neverfox Dec 12 '15 at 19:39
  • 1
    I wonder why this is not a problem with bootstrap-sass. There is no problem using the Glyphicons without the `publicPath` option. I don't see any big difference when looking at how Bootstrap loads the Glyphicon fonts in comparison to how Font Awesome does it. But there must be a difference. – medihack Feb 09 '16 at 20:39
  • 1
    @WillMadden, we're having a related issue: https://github.com/shakacode/bootstrap-loader/pull/56/files#diff-d9789595ca7b71d22ec4bd1531c19267R43 If you could take a look at the repro case, we'd be very grateful! We get the same exact error and we are using the extract-text-webpack-plugin.So "It's worth noting that if you're extracting your CSS to a separate file, you won't have this problem because your CSS will be in a proper file and URLs will be correctly resolved." is not applying to us. – justingordon Feb 28 '16 at 18:19
  • Yeah it works, but this is definitely not the preferred way of doing this. Thanks for the question and answer! – sidneydobber Mar 21 '16 at 16:28
  • 3
    Thanks I've found the cause of my SPA page reload problem from your answer. Just a note `publicPath: '/',` works too – Michael Buen Apr 12 '16 at 17:04
  • Its also possible to dynamically set your webpack `publicPath`, making your config more flexible. As demonstrated by this commit https://github.com/coryhouse/react-slingshot/pull/207/files – InSuperposition Aug 06 '16 at 19:41
  • I get the same result using "./" in publicPath – Bruno Quaresma Nov 10 '16 at 18:01
  • 2
    Actually this solution is not solving the issue on my env. is there anyway to upload to CodePen the entire webpack config and the scss (the relevant block)? thanks! – Idan Gozlan Nov 14 '16 at 17:22
  • Hmm. I'm getting this error but on all environments and I am using the `file-loader` .. will see what I can figure out – Damon Jun 29 '17 at 17:58
  • Is there a solution without using absolute paths? – colti Jul 11 '17 at 20:11
16

For me the problem was my regex expression. The below did the trick to get bootstrap working:

{
    test: /\.(woff|ttf|eot|svg)(\?v=[a-z0-9]\.[a-z0-9]\.[a-z0-9])?$/,
    loader: 'url-loader?limit=100000'
},
Eliran Malka
  • 15,821
  • 6
  • 77
  • 100
Waihibeachian
  • 311
  • 2
  • 3
  • 1
    removed woff2 from the test and it worked... { test: /\.(png|jpe?g|gif|svg|woff|ttf|eot|ico)$/, loader: 'file?name=assets/[name].[hash].[ext]' }, – Arun Kumar Jan 09 '17 at 12:22
13

As asnwered here by @mcortesi if you remove the sourceMaps from the css loader query the css will be built without use of blob and the data urls will be parsed fine

Community
  • 1
  • 1
Nadav SInai
  • 490
  • 3
  • 6
  • 1
    This answer works for me as well as the more popular answer above. – Jeremy S. Mar 01 '16 at 03:23
  • Thank you for this answer. Was having problems with with Storybook not loading Semantic UI's fonts, spent a good while looking for solutions. This fixed it. Just removing the one `?sourceMap` from the CSS loader. – greduan Jun 29 '17 at 08:57
  • Perfect! I don't have any problem with dropping source maps and my build needs to work without specifying the domain up front – Peter Mellett May 10 '18 at 13:35
5

As with @user3006381 above, my issue was not just relative URLs but that webpack was placing the files as if they were javascript files. Their contents were all basically:

module.exports = __webpack_public_path__ + "7410dd7fd1616d9a61625679285ff5d4.eot";

in the fonts directory instead of the real fonts and the font files were in the output folder under hash codes. To fix this, I had to change the test on my url-loader (in my case my image processor) to not load the fonts folder. I still had to set output.publicPath in webpack.config.js as @will-madden notes in his excellent answer.

Adam McCormick
  • 1,654
  • 18
  • 22
  • This is the issue I'm having, but since I'm using easy-webpack (probably misnamed, really), I'm not sure how to exclude the fonts folder from the url loader.. – Darren Oster Aug 09 '16 at 06:35
  • @DarrenOster: I think you have to specify it as an override using their [Object generators](https://github.com/easy-webpack/core#object-generators) so you can modify the generated config. – Adam McCormick Aug 09 '16 at 15:33
2

I experienced the same problem, but for different reasons.

After Will Madden's solution didn't help, I tried every alternative fix I could find via the Intertubes - also to no avail. Exploring further, I just happened to open up one of the font files at issue. The original content of the file had somehow been overwritten by Webpack to include some kind of configuration info, likely from previous tinkering with the file-loader. I replaced the corrupted files with the originals, and voilà, the errors disappeared (for both Chrome and Firefox).

user3006381
  • 2,735
  • 3
  • 23
  • 32
1

I know this doesn't answer OPs exact question but I came here with the same symptom but a different cause:

I had the .scss files of Slick Slider included like this:

@import "../../../node_modules/slick-carousel/slick/slick.scss";

On closer inspection it turned out that the it was trying to load the font from an invalid location (<host>/assets/css/fonts/slick.woff), the way it was referenced from the stylesheet.

I ended up simply copying the /font/ to my assets/css/ and the issue was resolved for me.

bpylearner
  • 507
  • 4
  • 12
1

Since you use url-loader:

The url-loader works like the file-loader, but can return a DataURL if the file is smaller than a byte limit.

So another solution to this problem would be making the limit higher enough that the font files are included as DataURL, for example to 100000 which are more or less 100Kb:

{
  module: {
    loaders: [
      // ...
      {
        test: /\.scss$/,
        loaders: ['style', 'css?sourceMap', 'autoprefixer', 'sass?sourceMap'],
      },
      {
        test: /images\/.*\.(png|jpg|svg|gif)$/,
        loader: 'url-loader?limit=10000&name="[name]-[hash].[ext]"',
      },
      {
        test: /\.woff(\?v=\d+\.\d+\.\d+)?$/,
        use: 'url-loader?limit=100000&mimetype=application/font-woff',
      },
      {
        test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/,
        use: 'url-loader?limit=100000&mimetype=application/font-woff',
      },
      {
        test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
        use: 'url-loader?limit=100000&mimetype=application/octet-stream',
      },
      {
        test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
        use: 'file-loader',
      },
      {
        test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
        use: 'url-loader?limit=100000&mimetype=image/svg+xml',
      },
    ],
  },
}

Allways taking into account on what the limit number represents:

Byte limit to inline files as Data URL

This way you don't need to specify the whole URL of the assets. Which can be difficult when you want Webpack to not only respond from localhost.

Just one last consideration, this configuration is NOT RECOMMENDED for production. This is just for development easiness.

Roc
  • 2,500
  • 2
  • 15
  • 18
1

The best and easiest method is to base64 encode the font file. And use it in font-face. For encoding, go to the folder having the font-file and use the command in terminal:

base64 Roboto.ttf > basecodedtext.txt

You will get an output file named basecodedtext.txt. Open that file. Remove any white spaces in that.

Copy that code and add the following line to the CSS file:

@font-face {
  font-family: "font-name";
  src: url(data:application/x-font-woff;charset=utf-8;base64,<<paste your code here>>) format('woff');
}  

Then you can use the font-family: "font-name" in your CSS.

Shayki Abramczyk
  • 36,824
  • 16
  • 89
  • 114
  • messier than other solutions posted, but it works and useful if you don't have direct access to your webpack config (e.g. if using CRA) – user2521119 Jan 19 '21 at 20:23
1

I just had the same issue with Font Awesome. Turned out this was caused by a problem with FTP. The file was uploaded as text (ASCII) instead of binary, which corrupted the file. I simply changed my FTP software to binary, re-uploaded the font files, and then it all worked.

https://css-tricks.com/forums/topic/custom-fonts-returns-failed-to-decode-downloaded-font/ this helped me in the end I had the same issue with FTP transferring files as text

0

If you're using Angular you need to check to make sure your

<base href="/"> 

tag comes before your style sheet bundle. I switched my code from this:

 <script src="~/bundles/style.bundle.js"></script>
 <base href="~/" />

to this:

 <base href="~/" />
 <script src="~/bundles/style.bundle.js"></script>

and the problem was fixed. Thanks to this post for opening my eyes.

Community
  • 1
  • 1
Patrick Graham
  • 972
  • 8
  • 18
0

As of 2018,

use MiniCssExtractPlugin

for Webpack(> 4.0) will solve this problem.

https://github.com/webpack-contrib/mini-css-extract-plugin

Using extract-text-webpack-plugin in the accepted answer is NOT recommended for Webpack 4.0+.

dsignr
  • 2,295
  • 2
  • 35
  • 45
0

The limit was the clue for my code, but I had to specify it like this:

use: [
  {
    loader: 'url-loader',
    options: {
      limit: 8192,
    },
  },
],
webjay
  • 5,358
  • 9
  • 45
  • 62
0

In my case adding following lines to lambda.js {my deployed is on AWS Lambda} fixed the issue.

 'font/opentype',
 'font/sfnt',
 'font/ttf',
 'font/woff',
 'font/woff2'
codepen
  • 409
  • 5
  • 9
0

Had same problem like @adam-mccormick and user3006381. I am using webpack5 and hot module.

My .eot or .woff files contained:

export default __webpack_public_path__ + "./fonts/montserrat-v24-latin-ext-regular.woff2";

and my webpack.config.js looked like:

modules.exports = {
//...
   module: {
      rules: [
         //...
         {
            test: /\.(woff|woff2|eot|ttf|otf)$/i,
            use: [{
               loader: "file-loader",
               options: {
                  name: './fonts/[name].[ext]',
               }
            }]
         }
      ]

   }
}

and this was my problem solution:

modules.exports = {
//...
   module: {
      rules: [
         //...
         {
            test: /\.(woff|woff2|eot|ttf|otf)$/i,
            type: 'asset/resource',
            generator: {
               filename: './fonts/[name].[ext]',
            }
         }
      ]

   }
}