How to prevent browser page caching in Rails

68,527

Solution 1

I finally figured this out - http://blog.serendeputy.com/posts/how-to-prevent-browsers-from-caching-a-page-in-rails/ in application_controller.rb.

After Ruby on Rails 5:

class ApplicationController < ActionController::Base

  before_action :set_cache_headers

  private

  def set_cache_headers
    response.headers["Cache-Control"] = "no-cache, no-store"
    response.headers["Pragma"] = "no-cache"
    response.headers["Expires"] = "Mon, 01 Jan 1990 00:00:00 GMT"
  end
end

Ruby on Rails 4 and older versions:

class ApplicationController < ActionController::Base

  before_filter :set_cache_headers

  private

  def set_cache_headers
    response.headers["Cache-Control"] = "no-cache, no-store"
    response.headers["Pragma"] = "no-cache"
    response.headers["Expires"] = "Mon, 01 Jan 1990 00:00:00 GMT"
  end
end

Solution 2

Use:

expires_now()

expires_now()

Solution 3

I have used this line with some success in the controller. It works in Safari and Internet Explorer, but I haven't seen it work with Firefox.

response.headers["Expires"] = "#{1.year.ago}"

For your second point, if you use the Ruby on Rails helper methods like

stylesheet_link_tag

and leave the default settings on your web server, the assets are typically cached pretty well.

Solution 4

Point of note: You can't conditionally clear the cache (like if a before_filter only calls reset_cache if the user's already been there). You need to unconditionally clear the cache, because the browser won't make a new request just to see if this time it needs to reload, even though it didn't need to last time.

Example:

before_filter :reset_cache, if: :user_completed_demographics?

won't work to prevent users from coming back after they've been there, since the browser uses the original cache headers on the Back button.

before_filter :reset_cache

will work, however (after refreshing the page and clearing the cache from before you added this, obviously), since, on the first request, the browser will get the no-cache, no-store, ... and apply it to future page loads.

Solution 5

no_cache_control Gem.

If you need to do this for all responses, e.g., to pass a penetration test (Burp Suite, Detectify, etc.), you can install this Gem on Ruby on Rails 4+ in order to add the following headers to all responses:

Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: -1

It works like a charm and is really the right way to go for secure, HTTPS web applications that require authentication to do anything.

Share:
68,527

Related videos on Youtube

Jason Butler
Author by

Jason Butler

Updated on May 11, 2022

Comments

  • Jason Butler
    Jason Butler about 2 years

    Ubuntu → Apache → Phusion Passenger → Rails 2.3.

    The main part of my site reacts to your clicks. So, if you click on a link, it will send you on to the destination, and instantly regenerate your page.

    But, if you hit the back button, you don't see the new page. Unfortunately, it's not showing up without a manual refresh; it appears the browser is caching it. I want to make sure the browser does not cache the page.

    Separately, I do want to set far-future expiration dates for all my static assets.

    What's the best way to solve this? Should I solve this in Ruby on Rails? Apache? JavaScript?


    Alas. Neither of these suggestions forced the behavior I'm looking for.

    Maybe there's a JavaScript answer? I could have Ruby on Rails write out a timestamp in a comment, and then have the JavaScript code check to see if the times are within five seconds (or whatever works). If yes, then fine, but if no, then reload the page?

    Do you think this would work?

    • Peter Mortensen
      Peter Mortensen about 2 years
      What is the context of the first line? Your site? Something in Ubuntu? What is "Phusion Passenger"?
    • Jason Butler
      Jason Butler about 2 years
      Phusion Passenger is an app server. It was pretty popular back then, I don't know if it's still widely used.
  • CafeHey
    CafeHey almost 12 years
    Should this be wrapped in a "if request.xhr?" so it only gets set on ajax refreshes but the normal pages do not?
  • Daniel Rikowski
    Daniel Rikowski over 11 years
    expires_now only sends the no-cache header. Depending on the browser this might not be enough. (For example Firefox wants a no-store for non-HTTPS connections: developer.mozilla.org/en/docs/Using_Firefox_1.5_caching )
  • Ashish Tonse
    Ashish Tonse over 11 years
    @Smickie I've done exactly that. I only needed my Ajax requests to not be cached. Otherwise this answer is perfect!
  • Archonic
    Archonic almost 11 years
    1.year.ago is unnecessary overhead. Just pick some arbitrary time in the past like Fri, 01 Jan 1990 00:00:00 GMT
  • Archonic
    Archonic almost 11 years
    I recommend wrapping in if request.xhr to only bust the cache for ajax requests like @Smickie mentioned. If you stop all caching for all requests, good luck scaling down the road.
  • gaqzi
    gaqzi over 9 years
    You only really need Cache-Control: no-store as long as the browser is compliant with HTTP 1.1. Section 14.9.2 What May be Stored by Caches
  • Jan Hettich
    Jan Hettich over 9 years
    Jan 1, 1990, was a Monday!
  • Thorin
    Thorin over 9 years
    Its not working for me I have add the same code in application_controller.rb and after logout I am able to see the last page by back button. Please guide me where I am wrong?
  • furiabhavesh
    furiabhavesh over 9 years
    Will this also NOT cache JS and CSS in rails app ? Will JS and CSS be loaded from server for each request ?
  • msdundar
    msdundar over 9 years
    Isn't it completely disabling caching for app? Because it will be located in application_controller.rb?
  • Sambit
    Sambit about 9 years
    This preventing caching for FF and Chrome, but not in IE 11. Is there anything more to do for IE??
  • Thomas R. Koll
    Thomas R. Koll about 6 years
    @Archonic 1 jan 1990 was a Monday!
  • Andrew Smith
    Andrew Smith over 4 years
    Agreed, this is not a valid solution, tested with Rails 5.2 and Chrome 77. no-store is also needed.
  • Peter Mortensen
    Peter Mortensen about 2 years
    The link is broken (DNS domain expiration?): "Hmm. We’re having trouble finding that site. We can’t connect to the server at blog.serendeputy.com."