Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

Already on GitHub? Sign in to your account

Feature Request: Leverage Browser Caching #134

Closed
raamdev opened this Issue Apr 28, 2014 · 35 comments

Comments

Projects
None yet
Owner

raamdev commented Apr 28, 2014

Google PageSpeed Insights Analysis often recommends to site owners that they "leverage browser caching".

Setting an expiry date or a maximum age in the HTTP headers for static resources instructs the browser to load previously downloaded resources from local disk rather than over the network.
Leverage browser caching

Several Quick Cache users have inquired about this and I think it would be a good idea to add this feature to Quick Cache Pro.

Requested here:
http://wordpress.org/support/topic/how-to-leverage-browser-caching-1
http://wordpress.org/support/topic/google-pagespeed-insights-suggests-leverage-browser-caching


Related: websharks#255 websharks#371


Next Actions

  • Create a new method that can inject Browser Caching code into the root .htaccess file (this should be a universal method that can be used for inserting other code into .htaccess as well, such as GZIP Compression)
  • Create a new ZenCache option to enable/disable Browser Caching
  • Create a new Options Panel for Browser Caching; this should detect two things: 1) if web server is not Apache v2.1+ and show a notice indicating that Browser Caching is not supported, and 2) if root .htaccess is not writable, a notice should be displayed indicating as much and show the Browser Caching code that needs to be manually inserted into the root .htaccess file.
  • On admin_init detect if Browser Caching option is enabled and Browser Caching code is not found in .htaccess file, it should display a warning
  • A KB Article should be written that further explains Browser Caching and answers some frequently asked questions.
  • Existing no-cache headers being enabled/disabled should be considered as part of this work. For instance, when browser caching is enabled via .htaccess, ZC should not send no-cache headers.
  • Determine best practice for users running Nginx (i.e., what Nginx users should add to their config file to enable browser caching).
  • Detect if user is running Nginx instead of Apache and show suggested config snippet to be added to their config file (requires root access, so user will need to handle that themselves).

KTS915 commented May 13, 2014

I have often found that recommendation on Pingdom too, so this sounds like a good idea to me!

+1

Owner

jaswrks commented Jun 3, 2014

I agree, so long as the site owner knows what they are doing. Currently, QC doesn't impact static content on the site at all; i.e. images, JS, CSS, etc. That is, the site continues to leverage browser caching on those resources in whatever way it chooses to do so. However, QC does (by default) send no-cache headers to the browser for PHP (i.e. dynamically generated content) built by WordPress. You can turn this on/off if you prefer though. That's already possible.

Generally speaking, if it has a .php extension I send no-cache headers to the browser; whether I am in WordPress or not; and whether I have QC running or not. There are some exceptions, but if the underlying HTML is being generated dynamically I don't want folks caching that HTML content; because it's always subject to change dynamically.

Remember, when QC sends a no-cache header, that doesn't impact static resources, it only impacts the HTML that loads them. In other words, the benefit of sending a no-cache header when serving content generated via PHP, far outweighs the very tiny performance hit there IMO. This is what allows your site to remain dynamic.

Imagine a user that visits Page A for the first time while logged-in. Their browser caches that version of the content. Now they log out of the site. The next time they visit Page A they will still see the same page they saw while logged into the site, because that's the version of the content that their browser cached first. Should it still be the same? No! Might it have their username at the top? Or maybe a link that says "logout"? ; even though they are no longer logged-in. See how this might create confusion?

Of course, that's just one scenario. On a site that generates content dynamically, there could be a few other scenarios similar to this one, where having one variation in the cache, might result in a visitor NOT seeing what you intended for them to see on a return visit. This is particularly hairy when your site operates on sessions or other client-side cookie values in one way or another.

All of that said, if getting every ounce of speed possible is more important that maintaining the integrity of the site itself (i.e. to be able to dynamically alter the HTML output via PHP when you need to); then you can certainly allow a browser to cache dynamically generated content. That's fine if you know exactly what you're doing. It would help to reduce the overall number of HTTP connections that your server deals with. It's just important to realize the impact of such a decision.

Owner

raamdev commented Jun 4, 2014

@jaswsinc Thank you for sharing your thoughts. That's definitely a very important point.

I wasn't under the impression that this GitHub issue was referring to caching HTML in the browser.

This is about allowing browser caching for all other resources that are more likely to be static, e.g., JS and CSS files, image files, and any other binary files that are unlikely to change. See this overview description from the Google PageSpeed Leverage Browser Caching recommendations page.

Browser caching for static resources can save a user time if they visit your site more than once. Caching headers should apply to all cacheable static resources, not just a small subset (such as images). Cacheable resources include JS and CSS files, image files, and other binary object files (media files, PDFs, etc.). In general, HTML is not static and shouldn't be considered cacheable by default. You should consider what caching policy would work well for your site’s HTML.

Owner

raamdev commented Jun 4, 2014

What I'm thinking is a section of Quick Cache that makes a recommendation for what should be inserted into their main .htaccess file, and perhaps even allows them to have Quick Cache insert it for them.

Here's an example of what that might look like:

## EXPIRES CACHING ##
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType image/jpg "access 1 year"
ExpiresByType image/jpeg "access 1 year"
ExpiresByType image/gif "access 1 year"
ExpiresByType image/png "access 1 year"
ExpiresByType text/css "access 1 month"
ExpiresByType text/html "access 1 month"
ExpiresByType application/pdf "access 1 month"
ExpiresByType text/x-javascript "access 1 month"
ExpiresByType application/x-shockwave-flash "access 1 month"
ExpiresByType image/x-icon "access 1 year"
ExpiresDefault "access 1 month"
</IfModule>
## EXPIRES CACHING ##
Owner

jaswrks commented Jun 4, 2014

@raamdev I see, thank you. Here is what I use in Apache to achieve this.

# Browser Caching

FileETag MTime Size

<IfModule expires_module>
    ExpiresActive on
    ExpiresDefault "access plus 1 week"
</IfModule>
Owner

jaswrks commented Jun 4, 2014

What I'm thinking is a section of Quick Cache that makes a recommendation for what should be inserted into their main .htaccess file, and perhaps even allows them to have Quick Cache insert it for them.

I agree with you on that. Great idea!

Owner

jaswrks commented Jun 4, 2014

Note the default value of FileETag is FileETag INode MTime Size. It's good to remove the INode from this. See: http://www.geekride.com/configure-remove-etags-apache-http-optimize-site/

Owner

raamdev commented Jun 4, 2014

Note the default value of FileETag is FileETag INode MTime Size. It's good to remove the INode from this. See: http://www.geekride.com/configure-remove-etags-apache-http-optimize-site/

Very good to know. Thanks for the tip! :)

Owner

jaswrks commented Jun 4, 2014

nd perhaps even allows them to have Quick Cache insert it for them.

I agree that we could certainly do this for them. I don't see any harm in doing so. So long as it's optional.

KTS915 commented Jun 4, 2014

Just a couple of comments as a user, not a programmer:

  1. What you both have come up with as the best way of approaching this sounds very helpful. Thank you!
  2. When the feature is included in a future version of QC, perhaps it would be a good idea to explain what this feature is for, and what it is not for (along the lines of Jason's initial explanation of potential pitfalls) so as to avoid users like me getting the wrong idea.

For example, I would think that anyone wanting to cache content for logged-in users should really be using that specific feature in QC Pro (especially if you're able to get s2Member to permit that without causing other issues).

Thanks again!

Owner

raamdev commented Jun 5, 2014

@KTS915 Thank you VERY much for the feedback. :) That's very helpful.

Owner

raamdev commented Jun 5, 2014

I found the following example configuration from @jaswsinc in my notes that I'm copying here for reference:

FileETag none
<IfModule mod_headers.c>
    Header unset etag
</IfModule>

<IfModule mod_expires.c>
    ExpiresActive on
    ExpiresByType text/plain "access plus 1 week"
    ExpiresByType text/css "access plus 1 week"
    ExpiresByType text/javascript "access plus 1 week"
    ExpiresByType application/javascript "access plus 1 week"
    ExpiresByType application/x-javascript "access plus 1 week"
    ExpiresByType image/svg+xml "access plus 1 week"
    ExpiresByType image/gif "access plus 1 week"
    ExpiresByType image/png "access plus 1 week"
    ExpiresByType image/ico "access plus 1 week"
    ExpiresByType image/x-icon "access plus 1 week"
    ExpiresByType image/jpg "access plus 1 week"
    ExpiresByType image/jpe "access plus 1 week"
    ExpiresByType image/jpeg "access plus 1 week"
    ExpiresByType font/truetype "access plus 1 week"
    ExpiresByType application/x-font-ttf "access plus 1 week"
    ExpiresByType font/opentype "access plus 1 week"
    ExpiresByType application/x-font-otf "access plus 1 week"
    ExpiresByType application/font-woff "access plus 1 week"
    ExpiresByType application/vnd.ms-fontobject "access plus 1 week"
    ExpiresByType application/x-shockwave-flash "access plus 1 week"
    ExpiresByType application/x-httpd-php-source "access plus 1 week"
</IfModule>

See also, more information on leveraging browser caching: http://gtmetrix.com/leverage-browser-caching.html

Owner

jaswrks commented Jun 5, 2014

@raamdev Since I originally posted that example, I spent some time going back through and trying to bring my own configurations up-to-date and also tried to simplify things just a bit further.


# Browser Caching
FileETag MTime Size

<IfModule expires_module>
    ExpiresActive on
    ExpiresDefault "access plus 1 week"
</IfModule>

Instead of disabling ETags, which actually help to alleviate the burden on a server, we can leave them enabled; and just exclude the INode. This way when caching is allowed, a browser can also use ETags to further optimize itself.

Instead of needing to worry about individual MIME types, we can throw a simple default Expires header of access plus 1 week (or whatever you think is best there). This way the configuration is not dependent upon the MIME types configured on a given server either.

So what does that mean?

  • All files (by default) are cacheable; and the cache expires after one week.
  • If WordPress (or another plugin) wants to disallow caching for a request it can call upon nocache_headers() in the WordPress core to override the headers passed by the server based on the default expiration time of one week.

Outside of WordPress, the case would be the same. If dynamic content wants to override the default Expires header it can simply do so on it's own via PHP's header() function.


In a case where a site owner wants to get more specific they could add new entries for various MIME types of their choosing. Anyway, just my thoughts. You might prefer to provide the example with a full list of MIME types. I think either way would be fine. Just wanted to point out that it doesn't necessarily need to be as complicated as my original example.

    ExpiresByType image/gif "access plus 1 week"
    ExpiresByType image/png "access plus 1 week"
    ExpiresByType image/ico "access plus 1 week"
Owner

raamdev commented Jun 5, 2014

All files (by default) are cacheable; and the cache expires after one week

But wouldn't that mean if WordPress (or maybe another WordPress plugin) did not explicitly override the default Expires header that HTML would be cached? For a dynamic application like WordPress, I would think that would nearly always be undesirable.

It's my understanding that by explicitly defining what file types should be cached we can avoid any possibility that dynamic HTML might accidentally get cached by the browser. Is that correct?

Owner

jaswrks commented Jun 5, 2014

But wouldn't that mean if WordPress (or maybe another WordPress plugin) did not explicitly override the default Expires header that HTML would be cached? For a dynamic application like WordPress, I would think that would nearly always be undesirable.

That's correct. I don't see that as a bad thing though. It really just simplifies thing a bit.

If the content does not state that it's NOT to be cached, it's left up to a browser anyway. For instance, if you access any file on the server and that file does not set an Expires header or a Cache-Control header; the browser just decides what to do on it's own; and that is generally to cache the content; since we didn't state anything to contrary.

In short, if a file should not be cached, headers should be sent to a browser. The following just establishes the default base that we start from.

# Browser Caching
FileETag MTime Size

<IfModule expires_module>
    ExpiresActive on
    ExpiresDefault "access plus 1 week"
</IfModule>
Owner

raamdev commented Jun 5, 2014

if a file should not be cached, headers should be sent to a browser.

Got it. Thank you for explaining that. I haven't looked into how WordPress Core handles this, but I assume it's pretty consistent with sending the necessary no-cache headers when it should, correct?

Owner

jaswrks commented Jun 5, 2014

pretty consistent with sending the necessary no-cache headers when it should, correct?

Yes, WordPress seems to do a good job of controlling the cache behavior on it's own; when it comes to the back end. Anything in the admin sends nocache_headers() from the WP core. Other static files on the server are generally left as-is; so a default server configuration would dictate how long those should be cached for. Following the example above, we might allow static resources (i.e. anything that doesn't explicitly say that it can't be cached) to be cached for up to 1 week at a time.

If we leave ETags enabled too, then a browser may actually cache it even longer than this; so long as the server says 304 Not Modified. Thus, the reason we want to leave ETags enabled. This works to further reduce load on the server when it comes to static resources.

Owner

raamdev commented Jun 5, 2014

Perfect. I agree then that it makes sense to keep things simple and leave the ETags enabled. In the configuration panel, I can maybe have an expandable section that explains how things can be tweaked further if setting expire times for specific file types is desired (or just link out to a wiki article).

+1 for this request.

👍

Owner

raamdev commented Nov 29, 2014

Noting that I had 1 request today via Quick Cache Pro support for this request.

+1 for this request.

lkraav commented May 9, 2015

What's the next step here?

Owner

raamdev commented May 12, 2015

@lkraav Thanks for the ping. I added a list of Next Actions to the top of this GitHub Issue.

@jaswsinc When you get a chance, could you review the next actions I added just in case I missed anything. ↑

Owner

jaswrks commented May 12, 2015

I added two more points to the bottom of the list above ↑

isaumya commented May 29, 2015

Hey guys this is a great idea but there is one thing I should point about. You see the best part of ZenCache pro is that it is not server specific. So please try to keep that. I see that you guys have considered this for an apache server. But if you check the current server trend, most Webmasters who has their own vps uses ngix instead apache. But as in nginx there is no htaccess and you can't populate the vhost file without root access, I think you guys should try to detect if its a nginx server, in that case show the user the exact vhost code they need to use to achieve this feature. Thanks again for your great work.

Owner

raamdev commented May 30, 2015

@isaumya Thank you for the feedback! :-) Yes, that's exactly what we plan to do with regards to Nginx--we'll come up with some best-practice suggestions and recommend those to users who we detect are running Nginx instead of Apache.

isaumya commented Jun 2, 2015

@raamdev Thats good approach mate :)

@raamdev raamdev added the FPWPR label Jun 11, 2015

Hi everyone. I just past the basic code in my .htaccess file and it works perfectly but i was wondering 1week can be a long to cache.I tried to reduce the period but it seems Google don't like it more less than 1 week :) . But now my question is, Is it also effect the Adsense ads? Advance thanks to you guys :)

Owner

raamdev commented Jun 29, 2015

@hamidpeya The AdSense JavaScript is not hosted on your server but rather is loaded from Google's servers, so the Leverage Browser Caching settings that you apply to your .htaccess file have no effect on the AdSense JavaScript.

AskKim commented Jul 5, 2015

I was just pestering support about this for ZenCache Pro and discovered it oddly wasn't included. Hopefully it will be rolled out soon.

@raamdev raamdev modified the milestone: Next Release (Pro) Jul 27, 2015

@raamdev raamdev referenced this issue in websharks/comet-cache-kb Aug 2, 2015

Open

How do I improve my Google PageSpeed score? #85

0 of 6 tasks complete

@raamdev raamdev modified the milestones: Next Release (Pro), Future Release (Pro) Aug 12, 2015

@raamdev raamdev modified the milestones: Next Release (Pro), Future Release (Pro) Sep 17, 2015

@raamdev raamdev modified the milestones: Next Release (Pro), Future Release (Pro) Oct 4, 2015

@raamdev raamdev modified the milestones: Next Release (Pro), Future Release (Pro) Oct 29, 2015

@raamdev raamdev modified the milestones: Next Release (Pro), Future Release (Pro) Dec 2, 2015

@raamdev raamdev modified the milestones: Next Release (Pro), Future Release (Pro) Dec 28, 2015

@raamdev raamdev modified the milestones: Next Release (Pro); 1st CC Notice, Future Release (Pro) Feb 13, 2016

@raamdev raamdev modified the milestones: Next Release, Future Release Feb 26, 2016

@raamdev raamdev modified the milestones: Next Release, Future Release Apr 4, 2016

@raamdev raamdev modified the milestones: Next Release, Future Release May 11, 2016

Owner

raamdev commented Jun 3, 2016

Noting that work on this feature has started in #765.

@raamdev raamdev modified the milestones: Next Release, Future Release Jun 20, 2016

@raamdev raamdev modified the milestones: v160706, Next Release Jul 7, 2016

Owner

raamdev commented Jul 7, 2016

Comet Cache v160706 has been released and includes changes from this GitHub Issue. See the v160706 announcement for further details.


This issue will now be locked to further updates. If you have something to add related to this GitHub Issue, please open a new GitHub Issue and reference this one (#134).

@raamdev raamdev closed this Jul 7, 2016

@raamdev raamdev locked and limited conversation to collaborators Jul 7, 2016

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