Project

General

Profile

Actions

Browser Caching

General Background: as to why caching in browser is a good thing

It is good practise on any website to let the browser cache objects that are static - ie don't contain user content, and are the same for hours, days or weeks.

(This in general means files like: .css, .js and image files)

But some content does change very fast, so must not be cached by the browser.

If the browser is told 'don't cache this object':
then it knows not to. This is what Websites (and Redmine) do on the pages that may change: eg an issue page: it will put into the HTTP header that message.

But in the absence of that - if the browser is not sure about a page component
that it already has recently downloaded, it will send a 304 request to the server: saying 'can you tell me, is this file still not stale'. And with a 304 the server does not need to send the whole object again: just a short 'yes, that is still fresh' answer.

In Redmine's default set-up: it generates a lot of 304 connects on every page: you can see these in a tool like Firebug. One for every css and .js file etc. Many.

These are bad for the user experience: because the browser has to wait for these responses, before it can carry on and build the page.

So we need to tell the browser that these objects will not be stale for a long time

There are easy ways to configure Apache and nginx to do this: by telling them to set the 'Expiry' date in the HTTP Header well into the future: so that the browser knows: 'OK, this objects is not stale, because we are still before the expiry date'.

Thus when a new user visits Redmine, their browser on the first page will GET the .css and .js files etc, but on pages after that: does not need to get them again.

The user will experience faster web page builds!

The Problem with Redmine

Unfortunately, Redmine (rather unwisely) uses .js file names, for things that DO contain user content: ie things that should NOT be cached in the browser.

So this means; if in Apache/nginx you add a simple config, to cache anyting that is named *.js: then your Redmine will break!

See the Issue #17770 - where this problem is reported, to see if the Redmine team can change it, to STOP using the .js in bad places.

Tne places it breaks if you use a simple config are: (a) when editing a journal in an issue (b) when uploading a file to an issue

The work around for Redmine

The simple case, that will break Redmine as above, because it does not care about which directory the js files are in:eg:

location ~* \.(ico|css|js|gif|jp?g|png)(\?[0-9]+)?$ {
expires 365d;
}

So need to use a more complex configuration: that also checks which directory the .js file is in, before setting the cache heading.

I got this working

        # the regex logic:   after either /javascripts/ or /stylesheets/ find the suffixes we want, followed by any quantity of numbers 0-9
        #     This works because the files we want to cache always appear after one of those 2 directories:  but not the files we want to ignore
        #        /journals/edit/24174.js   and /uploads.js?attachment_id=1&filename=my-file-to-upload.png
        location ~* /(?:(?:plugin_assets|themes).+)?(javascripts|stylesheets|images|favicon).+(css|js|jpg|gif|ico|png)(\?[0-9]+)$ {
            # add_header  X-SV-test 304-killer;    use this do-nothing HTTP Header, if you need to play with the regexp 
            #- for testing without fear of breaking anything!
            expires 365d;
        }

Since redmine 3.4.0 (#24617), static files requests have a ?timestamp appended to them, to avoid issues when upgrading redmine versions and cache serving the wrong files. However, custom themes don't append a timestamp to the request so application.css and responsive.css can break spectacularly. See #29625 for a workaround.

If you want to be safe, don't cache files which don't have a ?timestamp

-  # the regex logic: after either /javascripts/ or /stylesheets/ find the suffixes we want, followed by any quantity of numbers 0-9
+  # the regex logic: after either /javascripts/ or /stylesheets/ find the suffixes we want, followed by one or more numbers 0-9
   # This works because the files we want to cache always appear after one of those 2 directories:  but not the files we want to ignore
   # /journals/edit/24174.js  and /uploads.js?attachment_id=1&filename=my-file-to-upload.png
-  location ~* /(?<file>/(?:(?:plugin_assets|themes).+)?(?:javascripts|stylesheets|images|favicon).+(?:css|js|jpe?g|gif|ico|png|html|woff|ttf|svg)(\?[0-9]+)?$) {
+  location ~* /(?<file>/(?:(?:plugin_assets|themes).+)?(?:javascripts|stylesheets|images|favicon).+(?:css|js|jpe?g|gif|ico|png|html|woff|ttf|svg)(\?[0-9]+)$) {

Note that in nginx: 'expires' is the config that sets the 'Expiry' HTTP header in the object.

In the above, 365d means 365 days.

For full nginx 'expires' config details: http://nginx.org/en/docs/http/ngx_http_headers_module.html#expires
For Apache: see mod_expires: http://httpd.apache.org/docs/2.2/mod/mod_expires.html

Updated by salman mp 7 months ago · 19 revisions