In a Rails6 app with webpacker replaced by sprockets, I do not manage to let sprockets make my browser cache fonts. Edit: my browser does cache the font, but google complains and curl shows how the App responds (not as expected with a 304, see below).
Update
It seems that a 304
is only returned when you tell the server (via If-Modified-Since
-headers) that you know exactly the last modified version. While I Mozillas Dev Resources do not state that this should clearly be the case (and I am not in RFC-reading mood), it might make sens:
- your server serves the asset on 2020-01-01 (appreviated date for simplicity)
- a browser visits you and stores the asset alongside its date
- next day same browser revisits, asks server for asset and tells it the last known date (
2020-01-01
viaIf-Modified-Since
-header)- server answes
304
: You know that stuff already
- server answes
- next day a mistake happens and a dev-asset is served by the server
- browser revisits, gets new (but wrong asset with
Last modified
date of 2020-01-03) and stores it alongside that date - server admins remove the wrong dev asset
- next day, browser visits and tells server "I know the thing from yesterday"
- server tells browser: no, forgot that, the correct payload is this, and this is the timestamp: 2020-01-01.
In my tests below, I used If-Modified-Since
headers that did not correspond to the last (production) asset Timestamp. Thanks @bliof for help figuring that out.
As my ultimate goal was to make googles speed insight happy (now that I know that this 304- response works if all players behave well) I will follow Rails 5+ path of config.public_file_server.headers
(https://blog.bigbinary.com/2015/10/31/rails-5-allows-setting-custom-http-headers-for-assets.html). The Rails guides also point out how you would usually let your webserver (or CDN) handle the situation (https://guides.rubyonrails.org/asset_pipeline.html#in-production), but my stack works somewhat different.
Original follows
The fonts are in e.g. app/assets/fonts/OTF/SourceSansPro-BoldIt.otf
and correctly put in public/assets/OTF/...fingerprint...
(accompanied by a .gz variant). They are referenced via a SCSS font-face rule, pointing to a file with the respective fingerprint in it (using font-url()
).
When curl
ing these, I seem to never get a HTTP/1.1 304 Not Modified
, but a 200
with the given payload. With the other (JS, CSS) assets it works as expected.
I did not modify config/initializers/assets.rb
, as all the subdirectories and files should already be picked up (and the assets:precompile
output and content of public/assets
shows that it works).
Digging into the sprockets code at https://github.com/rails/sprockets/blob/9909da64595ddcfa1e7ee40ed1d99738961288ec/lib/sprockets/server.rb#L73 seems to indicate that maybe an etag is not set correctly or something like that, but I do not really grock that code.
The application is deployed with dokku (basically a heroku) with a pretty standard nginx-configuration as far as I can tell: https://github.com/dokku/dokku/blob/master/plugins/nginx-vhosts/templates/nginx.conf.sigil . The app serves the assets itself (like in heroku).
What do I have to do such that sprockets adds the relevant headers / responds "correctly" with a 304
? Any ideas how to debug that issue?
The relevant "debugging" parts
The initial request for CSS
curl -v https://...application-3d...c76c3.css \
-H 'Accept: text/css,*/*;q=0.1'\
-H 'Accept-Language: en-US,en;q=0.5'\
--compressed # omitted: ... User-Agent, DNT, ...
# omitted: TLS handshake etc
> GET /assets/application-3d...c76c3.css HTTP/1.1
> Host: #the host
> Accept-Encoding: deflate, gzip
> User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:75.0) Gecko/20100101 Firefox/75.0
> Accept: text/css,*/*;q=0.1
> Accept-Language: en-US,en;q=0.5
> Referer: #the host
> DNT: 1
> Connection: keep-alive
> Cookie: #a cookie
>
< HTTP/1.1 200 OK
< Server: nginx
< Date: Tue, 21 Apr 2020 15:39:47 GMT
< Content-Type: text/css
< Content-Length: 41256
< Connection: keep-alive
< Last-Modified: Mon, 06 Apr 2020 11:59:56 GMT
< Content-Encoding: gzip
< Vary: Accept-Encoding
<
# payload
Subsequent fetch of CSS
(The relevant parts, other params and output omitted). Note that a If-Modified-Since: Mon, 06 Apr 2020 11:59:56 GMT header is sent along.
curl -v 'https://.../assets/application-3d...c76c3.css' \
-H 'If-Modified-Since: Mon, 06 Apr 2020 11:59:56 GMT'\
-H 'Cache-Control: max-age=0'
> If-Modified-Since: Mon, 06 Apr 2020 11:59:56 GMT
> Cache-Control: max-age=0
>
< HTTP/1.1 304 Not Modified
< Server: nginx
< Date: Tue, 21 Apr 2020 15:50:52 GMT
< Connection: keep-alive
(Thats what I want: A 304 Not Modified.
The initial request for the font asset
curl -v 'https://.../assets/WOFF2/TTF/SourceSansPro-Light.ttf-32...d9.woff2' \
-H 'Accept: application/font-woff2;q=1.0,application/font-woff;q=0.9,*/*;q=0.8'\
-H 'Accept-Language: en-US,en;q=0.5'\
--compressed \
-H 'Referer: https://...assets/application-3d....c76c3.css'
# ommitted: User Agent, Cookies, ....
> GET /assets/WOFF2/TTF/SourceSansPro-Light.ttf-32...d9.woff2 HTTP/1.1
> Host: #the host
> Accept-Encoding: deflate, gzip
> User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:75.0) Gecko/20100101 Firefox/75.0
> Accept: application/font-woff2;q=1.0,application/font-woff;q=0.9,*/*;q=0.8
> Accept-Language: en-US,en;q=0.5
> DNT: 1
> Connection: keep-alive
> Referer: https://.../assets/application-3d...c76c3.css
# cookie etc
>
< HTTP/1.1 200 OK
< Server: nginx
< Date: Tue, 21 Apr 2020 15:45:34 GMT
< Content-Type: application/font-woff2
< Content-Length: 88732
< Connection: keep-alive
< Last-Modified: Wed, 25 Mar 2020 20:09:14 GMT
<
# payload
Subsequent fetch of Font
curl -v 'https://.../assets/WOFF2/TTF/SourceSansPro-Light.ttf-32...ed9.woff2' \
-H 'Referer: https://.../assets/application-3d...c76c3.css'\
-H 'If-Modified-Since: Mon, 06 Apr 2020 11:59:56 GMT'
-H 'Cache-Control: max-age=0'
# ....
> If-Modified-Since: Mon, 06 Apr 2020 11:59:56 GMT
> Cache-Control: max-age=0
>
< HTTP/1.1 200 OK
< Server: nginx
< Date: Tue, 21 Apr 2020 15:53:46 GMT
< Content-Type: application/font-woff2
< Content-Length: 88732
< Connection: keep-alive
< Last-Modified: Wed, 25 Mar 2020 20:09:14 GMT
# payload
What I find interesting, that the server actually sends a Last-Modified which is way before the If-Modified-Since. I guess clever browsers stop the conversation there, but I really want to see a well-behaved 304.