0

Here's a snippet of my .htaccess file:

# ------------------------------------------------------------------------------
# | ETag removal                                                               |
# ------------------------------------------------------------------------------

# Since we're sending far-future expires headers (see below), ETags can
# be removed: http://developer.yahoo.com/performance/rules.html#etags.

# `FileETag None` is not enough for every server.
<IfModule mod_headers.c>
    Header unset ETag

  <filesMatch "\.(ico|jpe?g|png|gif|swf)$">
    Header set Cache-Control "max-age=2592000, public"
  </filesMatch>
  <filesMatch "\.(css)$">
    Header set Cache-Control "max-age=604800, public"
  </filesMatch>
  <filesMatch "\.(js)$">
    Header set Cache-Control "max-age=216000, public"
  </filesMatch>
  <filesMatch "\.(x?html?|php)$">
    Header set Cache-Control "max-age=600, private, must-revalidate"
  </filesMatch>
</IfModule>

FileETag None

# ------------------------------------------------------------------------------
# | Expires headers (for better cache control)                                 |
# ------------------------------------------------------------------------------

# The following expires headers are set pretty far in the future. If you don't
# control versioning with filename-based cache busting, consider lowering the
# cache time for resources like CSS and JS to something like 1 week.

<IfModule mod_expires.c>

    ExpiresActive on
    ExpiresDefault                                      "access plus 1 month"

  # CSS
    ExpiresByType text/css                              "access plus 1 year"

  # Data interchange
    ExpiresByType application/json                      "access plus 0 seconds"
    ExpiresByType application/xml                       "access plus 0 seconds"
    ExpiresByType text/xml                              "access plus 0 seconds"

  # Favicon (cannot be renamed!)
    ExpiresByType image/x-icon                          "access plus 1 week"

  # HTML components (HTCs)
    ExpiresByType text/x-component                      "access plus 1 month"

  # HTML
    ExpiresByType text/html                             "access plus 0 seconds"

  # JavaScript
    ExpiresByType text/javascript                       "access plus 1 year"
    ExpiresByType application/javascript                "access plus 1 year"
    ExpiresByType application/x-javascript              "access plus 1 year"

  # Manifest files
    ExpiresByType application/x-web-app-manifest+json   "access plus 0 seconds"
    ExpiresByType text/cache-manifest                   "access plus 0 seconds"

  # Media
    ExpiresByType audio/ogg                             "access plus 1 month"
    ExpiresByType image/gif                             "access plus 1 month"
    ExpiresByType image/jpeg                            "access plus 1 month"
    ExpiresByType image/jpg                            "access plus 1 month"
    ExpiresByType image/png                             "access plus 1 month"
    ExpiresByType video/mp4                             "access plus 1 month"
    ExpiresByType video/ogg                             "access plus 1 month"
    ExpiresByType video/webm                            "access plus 1 month"

  # Web feeds
    ExpiresByType application/atom+xml                  "access plus 1 hour"
    ExpiresByType application/rss+xml                   "access plus 1 hour"

  # Web fonts
    ExpiresByType application/font-woff                 "access plus 1 month"
    ExpiresByType application/vnd.ms-fontobject         "access plus 1 month"
    ExpiresByType application/x-font-ttf                "access plus 1 month"
    ExpiresByType font/opentype                         "access plus 1 month"
    ExpiresByType image/svg+xml                         "access plus 1 month"

</IfModule>

As can be seen, I have Cache-Control and Expires in there in the appropriate syntax. And yet Google PageSpeed tool says my JS isn't leveraging browser caching. Is there anything I'm missing to add?

I understand it's not good practice to include both Cache-Control and Expires Headers simultaneously. So I removed the Cache-Control section. The local file (am not concerned with third-party externals at the moment) is still showing as uncached when run past Google. The file that needs to be cached is http://www.peppyburro.com/sandboxassets/js/burroinline.js. The .htaccess in question is located at the document root, i.e. http://www.peppyburro.com/.htaccess. An .htaccess with the above caching snippet also resides in the /js folder that contains burroinline.js.

To add to the confusion, https://www.giftofspeed.com/cache-checker/ says my file is caching as expected whereas Google PageSpeed and GTMetrix say it isn't.

UPDATE: Looks like my CDN (CloudFlare) has something to do with caching issues because once I disabled CF, the caching kind of started working. I say kind of because Google PageSpeed randomly alternates between cached and un-cached despite nothing changing in my htaccess! Also, GTMetrix still shows the file as un-cached. Here's what the header looks like on my browser:

**General**
Request URL:http://peppyburro.com/sandboxassets/js/burroinline.js
Request Method:GET
Status Code:200 OK (from disk cache)
Remote Address:209.99.16.94:80
**Response Headers**
Accept-Ranges:bytes
Access-Control-Allow-Origin:*
Age:0
Cache-Control:max-age=216000, public
Content-Encoding:gzip
Content-Length:38611
Content-Type:application/javascript
Date:Wed, 01 Mar 2017 16:22:41 GMT
Expires:Thu, 01 Mar 2018 16:22:41 GMT
Last-Modified:Wed, 01 Mar 2017 02:18:53 GMT
Server:Apache Phusion_Passenger/4.0.10 mod_bwlimited/1.4 mod_fcgid/2.3.9
Vary:Accept-Encoding,User-Agent
Via:1.1 varnish-v4
X-Varnish:31524632

Does this mean the file is actually getting cached? If so, what could be done to have the same reflected consistently in Google PageSpeed and GTMetrix? And how could I make it work with CF enabled?

PS: Being on a shared host, I don't have access to httpd.conf.

TheLearner
  • 2,813
  • 5
  • 46
  • 94
  • I would advise before looking at the single local `.js` file you're having problems with, that you look at all the external JavaScript files that you're calling. You have quite a few of them and it will have an impact on your websites speed. See [Leverage Browser Caching 3rd Party JS](http://stackoverflow.com/questions/38376871/leverage-browser-caching-for-3rd-party-js/38377857#38377857). I would also avoid the use of varvy, it is not a trust worthy source, use [GTMetrics](https://gtmetrix.com/reports/www.peppyburro.com/Cev47MVX) and [iwebchk](https://iwebchk.com/) instead. – Joe Mar 01 '17 at 15:21

1 Answers1

2

EDIT: The answer ended up being a conflict between CloudFlare and the .htaccess file. The comments on this post discuss the troubleshooting and resolution of this issue.

I ran this resource through pingdom's tools to see what the request/response looked like.

https://tools.pingdom.com/#!/d8QPQx/http://www.peppyburro.com/sandboxassets/js/burroinline.js

It is in fact not being cached. The header is set to no-cache.

"no-cache" indicates that the returned response can't be used to satisfy a subsequent request to the same URL without first checking with the server if the response has changed. As a result, if a proper validation token (ETag) is present, no-cache incurs a roundtrip to validate the cached response, but can eliminate the download if the resource has not changed.

Source: https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching

The response from the server is:

Cache-Control public, max-age=216000

Because it is javascript, it may be advisable to extend this to a week or more. Additionally, the response here is public, while your setting is

Header set Cache-Control "max-age=216000, private"

The age is correct, but the visibility is a disparity.

"public" vs. "private"

If the response is marked as "public", then it can be cached, even if it has HTTP authentication associated with it, and even when the response status code isn't normally cacheable. Most of the time, "public" isn't necessary, because explicit caching information (like "max-age") indicates that the response is cacheable anyway.

By contrast, the browser can cache "private" responses. However, these responses are typically intended for a single user, so an intermediate cache is not allowed to cache them. For example, a user's browser can cache an HTML page with private user information, but a CDN can't cache the page.

Source: https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching

I am seeing some cloudflare (CDN) headers as well, marking that it is not cached. Typically, private responses are intended for sensitive content. I would first try to set this as public, but only if you are not concerned with sensitive information.

If you are concerned with sensitive information leave this as private.

While I think this may be the problem, there are several other factors (centered around a CDN) that could also be contributing to the problem.

Accept-Ranges:bytes
Access-Control-Allow-Origin:*
Age:0
Cache-Control:public, max-age=216000
CF-Cache-Status:MISS
CF-RAY:338d062cb1035a6e-BOS
Connection:Keep-Alive
Content-Type:application/javascript
Date:Wed, 01 Mar 2017 15:07:08 GMT
Expires:Sat, 04 Mar 2017 03:07:08 GMT
Last-Modified:Wed, 01 Mar 2017 02:18:53 GMT
Proxy-Connection:Keep-Alive
Server:cloudflare-nginx
Vary:Accept-Encoding
Via:1.1 varnish-v4
X-Varnish:18615326

These are the response headers from the server. They include a "MISS" in CF (cloudflare) caching. Additionally, here the cache control is also set to public.

Because of this, I think that the intermediate CDN may be causing caching issues.

If you have any additional information to provide (such as CDN/CloudFlare information), I would be happy to take another look.

malodie
  • 46
  • 5
  • I changed Cache-Control to "public" and that didn't do anything to the issue. The file is still not caching. I even tried removing the Cache-Control snippet altogether since I already have Expires headers in my htaccess, but the problem continues. As for CDN, yes, I am using CloudFlare but not sure what information to provide here in particular. – TheLearner Mar 01 '17 at 15:49
  • Do you think having another htaccess with these caching instructions in the *js* folder could make a difference? – TheLearner Mar 01 '17 at 15:53
  • I'd say that the extra file mentioned above is worth a try. I'm curious though if the caching is still being blocked by CF. I will do some more research and see what I can uncover. Thanks for testing the first idea though. Always good to rule things out. – malodie Mar 01 '17 at 15:59
  • Another idea. Can you change max-age=216000 to be the same as your css? Or, better yet, just append it with a | to the rest of your filetypes. E.G. : Header set Cache-Control "max-age=2628000, public" . There's also a ? in the middle of your jpeg. – malodie Mar 01 '17 at 16:33
  • I changed the value to 604800 for both css and js, and also added the snippet to a separate .htaccess in the **js** folder containing the file in question (burroinline.js). Yet no sign of browser caching. :( – TheLearner Mar 01 '17 at 16:47
  • Do you have full webserver access? – malodie Mar 01 '17 at 16:56
  • Are you thinking httpd.conf? If so, no because I'm on a shared host. – TheLearner Mar 01 '17 at 16:58
  • Yeah. I was thinking along that route. Not that it is necessary, but it could help. Any CloudFlare configs that you can work with? CloudFlare should not be blocking anything for you (as it should accept your .htaccess configs), but it is worth looking at if you haven't already. – malodie Mar 01 '17 at 17:08
  • There is no CF config that I am aware of being provided. I just signed up through my cpanel and it worked out of the box. But you were right! CF does seem to be the culprit because I just turned it off and it worked! The issue, however, is that it doesn't seem to be a wise move to just go off-CDN. There's got to be a way to make CDN and my htaccess coexist happily. – TheLearner Mar 02 '17 at 05:24
  • Aha! Hey. At least you tracked it down! So, it looks like a lot of people are having trouble getting CloudFlare to honor their .htaccess files. The first answer here: http://stackoverflow.com/questions/34910872/cache-control-headers-not-respected-on-cloudflare seems to have a lot of options (and other links). There should be some way that you can configure CF...hopefully! – malodie Mar 02 '17 at 13:41
  • Alright, guess what, I didn't even have to tweak CF settings. I just removed the Cache-Control bit altogether (since I already have Expires headers in my htaccess, and everything seems to work consistently now. I will still review my CF settings just to get educated but it's a surprise this redundancy (using Cache-Control and Expires headers simultaneously) could break caching. Wonder why. – TheLearner Mar 02 '17 at 14:01
  • Awesome! Glad it is fixed. I'm not sure why it is causing issues either, as it really shouldn't. If you find anything interesting, I'd love to hear about it. – malodie Mar 02 '17 at 16:10
  • Care to take a look at a related question I just posted at http://stackoverflow.com/questions/42559235/changes-in-htaccess-expires-header-not-reflecting-in-browser? – TheLearner Mar 02 '17 at 16:34
  • Yup. I'll head over there. I have a few ideas. – malodie Mar 02 '17 at 17:39