Skip to content

Instantly share code, notes, and snippets.

@anotheruiguy
Last active June 24, 2024 22:11
Show Gist options
  • Save anotheruiguy/7379570 to your computer and use it in GitHub Desktop.
Save anotheruiguy/7379570 to your computer and use it in GitHub Desktop.
Custom Web Fonts and the Rails Asset Pipeline

Web fonts are pretty much all the rage. Using a CDN for font libraries, like TypeKit or Google Fonts, will be a great solution for many projects. For others, this is not an option. Especially when you are creating a custom icon library for your project.

Rails and the asset pipeline are great tools, but Rails has yet to get caught up in the custom web font craze.

As with all things Rails, there is more then one way to skin this cat. There is the recommended way, and then there are the other ways.

The recommended way

Here I will show how to update your Rails project so that you can use the asset pipeline appropriately and resource your files using the common Rails convention.

The default asset pipeline

When looking in your project's assets directory, you would see the following:

|-app/
|---assets/
|-----images/
|-----javascripts/
|-----stylesheets/

What we need to do is add the fonts directory within the assets directory so that we can resource these files in our CSS or Sass files using proper rails conventions and the asset pipeline.

The problem is, simply adding a fonts directory isn't picked up by the pipeline. For now, let's just add that in there and then fix the Rails issue next.

|-app/
|---assets/
|-----fonts/
|-----images/
|-----javascripts/
|-----stylesheets/

Updating the asset pipeline

The fix is pretty simple. Open your project's config file, located at config/application.rb and add the following line within your Application class:

config.assets.paths << Rails.root.join("app", "assets", "fonts")

BOOM! Now Rails is smart enough to know what to do with assets within the fonts directory.

Fonts path in your Sass

Default CSS that you will get from sites like icomoon.io will typically look something like this:

@font-face {
	font-family: 'icofonts';
	src:url('fonts/icofonts.eot');
	src:url('fonts/icofonts.eot?#iefix') format('embedded-opentype'),
		url('fonts/icofonts.ttf') format('truetype'),
		url('fonts/icofonts.woff') format('woff'),
		url('fonts/icofonts.svg#icofonts') format('svg');
	font-weight: normal;
	font-style: normal;
}	

That seems right, unless you are using the asset pipeline. So to make the path correct we need to make a slight update and replace src:url() with src:font-url(). Our Sass file would look like:

@font-face {
  font-family:'icofonts';
  src:font-url('icofonts.eot');
  src:font-url('icofonts.eot?#iefix') format('embedded-opentype'),
  
  ...
} 

When the Sass is rendered into CSS, you should see something like the following:

@font-face {
  font-family: 'icofonts';
  src: url(/assets/icofonts.eot);
  src: url(/assets/icofonts.eot?#iefix) format("embedded-opentype"), 
    url(/assets/icofonts.ttf) format("truetype"), 
    url(/assets/icofonts.woff) format("woff"), 
    url(/assets/icofonts.svg#icofonts) format("svg");
  font-weight: normal;
  font-style: normal;
}

Perfect. Our fonts are in the assets directory where it feels best. Our code is clean and follows all the common Rails conventions. This is the best possible soliton and if you are happy here, do not read the rest of this article. It get's a little weird from here on.

The way of the hacker

Anything you put into a directory within the assets directory will be carried through the pipeline. So, why not put the fonts directory within the stylesheets directory?

If you are like me, that just feels weird, but I don't judge. If you don't want to or can't create the fonts directory within the assets directory or can't update the application.rb file, you could do something like the following:

|-app/
|---assets/
|-----images/
|-----javascripts/
|-----stylesheets/
|-------fonts/

The Sass

This is kind of interesting. Since the fonts directory is in the stylesheets directory, the fonts just come along for the ride. Simply use (asset-path(' ... ')) so that Rails creates the proper path.

@font-face {
  font-family:'icofonts';
  src:url(asset-path('fonts/icofonts.eot'));
  src:url(asset-path('fonts/icofonts.eot?#iefix')) format('embedded-opentype'),
    
  ...
}

When the Sass is processed into CSS, you should see the following:

@font-face {
  font-family:'icofonts';
  src: url('/assets/fonts/icofonts.eot');
  src: url('/assets/fonts/icofonts.eot?#iefix') format("embedded-opentype"),
  
  ...
}

Hardcoding the path, it works, but ... yuk

What's interesting about this is, since you are putting the fonts directory inside the stylesheets directory, do you really need to use the src:url(asset-path('fonts/icofonts.eot')); method? At this point the fonts are already in the asset pipeline and the following code, although not ideal, hardcoding /assets/ into the path will work.

@font-face {
  font-family:'icofonts';
  src:url('/assets/fonts/icofonts.eot');
  src:url('/assets/fonts/icofonts.eot?#iefix') format("embedded-opentype"),
  ...
}

But ... since the fonts directory is within the stylesheets directory and this is already in the asset pipeline, if you just refer to the fonts in a relative path, that works too. But man, that's just doesn't feel very Rails to me.

@font-face {
  font-family:'icofonts';
  src:url('fonts/icofonts.eot');
  src:url('fonts/icofonts.eot?#iefix') format("embedded-opentype"),
  ...
}

But ... I'm not using Sass?

Ok, first I have to ask ... WAT? Ok fine, not using Sass, going with good 'ol CSS. Interesting enough, putting the fonts directory is within the stylesheets directory again works and you can simply use the relative path.

@font-face {
  font-family: 'icofonts';
  src:url('fonts/icofonts.eot');
  src:url('fonts/icofonts.eot?#iefix') format("embedded-opentype"),
  ...
}

We all know that this kind of sucks. So if we wanted to bring the asset pipeline back into play we can't use CSS by itself, we need to add some Ruby juice to this. But how? Yup, make the file name.css.erb, yeah, I said that.

Ok, so for this example we need to get straight up Ruby on this. The ('fonts/icofonts.eot') needs to be wrapped in some Ruby <%= asset_path('...') %>;

@font-face {
  font-family: 'icofonts';
  src:url('<%= asset_path('fonts/icofonts.eot') %>');
  src:url('<%= asset_path('fonts/icofonts.eot?#iefix') %>') format('embedded-opentype'),
  ...
}

This is very similar to the earlier syntax of src:url(asset-path('fonts/ ... ')); I illustrated earlier, but WOW! css.erb and '<%= ... %>' just causes me a little pain.

You can eliminate this by simply changing any .css file to a .css.scss file and then take full advantage of all the awesome that Sass brings you and not hack Ruby onto plain CSS. Just my $0.02.

In conclusion

Although I am illustrating seemingly clever ways to solve this problem, in the end the best solution really is to update your Rails project and use the asset pipeline as intended. And if you aren't using Sass yet in your project and want to learn, I strongly suggest looking at Sass 101 - A Newb's Guide.

I should also note that many of these hacked techniques I did not test in a development environment with cache busting. Use at your own risk.

@yash-doshi
Copy link

You rock !! Thanks a ton for sharing this

@sathishjayapal
Copy link

Thanks., I think you meant to say

This is the best possible solution and if you are happy here, do not read the rest of this article. It get's a little weird from here on.

@chuggingCoffee
Copy link

This is great. Thank you for sharing.

@thetrevorharmon
Copy link

thetrevorharmon commented Jun 6, 2017

Exactly what I needed to solve my issue. Thanks for taking the time to post this!

@mattmartini
Copy link

This issue caused me lots of headaches until I found the mixin here Font Face

To make this work with asset pipeline in Rails 4.2 I changed url to font-url.

$src: append($src, font-url(quote($path + "." + $extmod)) format(quote($format)), comma);

@allthesignals
Copy link

Thank you so much, but I just have to say, what is SO maddening about the asset pipeline is how hard it is to debug. Not sure which Rails Way doctrine I'm missing, but it's always a surprise when I deploy to staging.

@WyattReid
Copy link

Helped me out a ton man, I'm in an enterprise setting where the "RailsGem-font-awesome" had to go through a lengthy security review and complex import process. I needed my icons now and this guide made that happen! Much appreciated!

@kovenko
Copy link

kovenko commented Oct 16, 2017

How to add font awesome to Rails 4 w/o gem
Rails 4.2.5.1
`
$ /app/assets/fonts/font-awesome
fontawesome-webfont.eot fontawesome-webfont.ttf fontawesome-webfont.woff2
fontawesome-webfont.svg fontawesome-webfont.woff

$ /app/assets/stylesheets/application.scss or $ /app/assets/stylesheets/application.css
*= require_tree .
*= require_self
*/
@import "font-awesome";

$ /app/assets/stylesheets/font-awesome.scss.erb or $ /app/assets/stylesheets/font-awesome.css.erb
@font-face {
font-family: 'FontAwesome';
src: url(<%= asset_path("font-awesome/fontawesome-webfont.eot") %>);
src: url(<%= asset_path("font-awesome/fontawesome-webfont.eot#iefix") %>) format("embedded-opentype"),
url(<%= asset_path("font-awesome/fontawesome-webfont.woff2") %>) format("woff2"),
url(<%= asset_path("font-awesome/fontawesome-webfont.woff") %>) format("woff"),
url(<%= asset_path("font-awesome/fontawesome-webfont.ttf") %>) format("truetype"),
url(<%= asset_path("font-awesome/fontawesome-webfont.svg#fontawesomeregular") %>) format("svg");
font-weight: normal;
font-style: normal;
}
.fa {
display: inline-block;
font: normal normal normal 14px/1 FontAwesome;
font-size: inherit;
text-rendering: auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
--- more ---

$ /app/views/page/index.html.erb
<i class="fa fa-camera-retro fa-5x"></i>
`
$ rake assets:precompile

@MSCAU
Copy link

MSCAU commented Sep 24, 2018

Really well written and helpful, thanks.

I just tried this on Rails 5.2, though, and suggest you note the following:

Instead of adding the line to config/application.rb as you suggested, since this creates an error:

undefined local variable or method config' for main:Object (NameError)`

when trying to start the server (which incidentally it would be good to remind people about), I found I needed to add a similar line to config/initializers/assets.rb instead:

Rails.application.config.assets.paths << Rails.root.join("app", "assets", "fonts")

Along with swapping url for font-url in FA's all.css, renaming all.css to all.scss (so that Rails does the preprocessing and actually understands "font-url") and putting the FA font files (EOT, WOFF, etc.) in a font folder (I used vendor/assets/fonts but could have used app/assets/fonts), this all works nicely in development and production.

@peytr
Copy link

peytr commented Dec 2, 2018

Great work, very helpful, thank you.
And also thanks to @MSCAU for the Rails 5.2 additional steps.
Worked perfectly!

@ladiadeniran
Copy link

Thanks was really helpful and I did make the changes suggested by @MSCAU

@matedemorphy
Copy link

any of those methods will work in rails 6?

@rodinux
Copy link

rodinux commented Jun 5, 2020

It works for me for an app rails 6.0.3.1 to resolve issues with rails-font-awesome and assets precompile. Thanks

@namautravail
Copy link

Hello,
Is it possible to prevent Rails rendering the page before the font is already loaded ? Or at least, to know by code, whether a font is loaded or not : a callback when font-url is finished to load , for example ? I did not see anything like that.

I'm using the "text-security" font to hide the text that the user is taping in an input field. So, before authorizing the user to tape the text, I should ensure the font is loaded.
Thank you for any idea.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment