27

This is how we prevent caching of JS and CSS files by browsers. This seems slightly hacky.. is there a better way?

<%
//JSP code
long ts = (new Date()).getTime(); //Used to prevent JS/CSS caching
%>

<link rel="stylesheet" type="text/css" media="screen" href="/css/management.css?<%=ts %>" />
<script type="text/javascript" src="/js/pm.init.js?<%=ts %>"></script> 
<script type="text/javascript" src="/js/pm.util.func.js?<%=ts %>"></script> 

Update: The reason we want to prevent caching is to ensure the newer version of the files are loaded when we do a new release.

Marcus Leon
  • 55,199
  • 118
  • 297
  • 429

7 Answers7

27

You want CSS and JS to be cached. It speeds up the loading of the web page when they come back. Adding a timestamp, your user's will be forced to download it time and time again.

If you want to make sure they always have a new version, than have your build system add a build number to the end of the file instead of a timestamp.

If you have issues with it just in dev, make sure to set up your browsers to not cache files or set headers on your dev pages to not cache.

epascarello
  • 204,599
  • 20
  • 195
  • 236
  • "Adding a timestamp, your user's will be forced to download it time and time again." (This makes sense). But then you said, "If you want to make sure they always have a new version, than have your build system add a build number to the end of the file instead of a timestamp." (This seems to contradict your first statement). – AlvinfromDiaspar Feb 20 '18 at 17:27
  • 2
    Love when 8 year old questions get comments. Well one you update everytime, the other you only update when it changes. Ideally you set proper cache headers and you would not need to do it. – epascarello Feb 20 '18 at 17:29
  • 1
    "set headers on your dev pages to not cache.". How do you do this exactly, these days? – Stealth Rabbi Apr 08 '18 at 00:51
  • Is there any reason to append something to the js file name when it changes? Why not just use `Cache-Control: no-cache`? – David Klempfner Sep 02 '21 at 05:13
22

Caching is your friend. If browsers are caching these files incorrectly, it means something is wrong with the HTTP headers your web server is sending along with the JS and CSS files themselves (not the HTML page that uses them). The browser uses those headers to figure out if it can cache the file.

Your Web server can send these headers (on every JS and CSS file it serves) to tell browsers not to cache them:

Cache-Control: no-store
Pragma: no-cache
Expires: Sat, 01 Jan 2000 00:00:00 GMT

But that will increase the network load on your site, and users will see the page load slower. You could be a little more lenient and allow the browser to cache the CSS file for 60 seconds:

Cache-Control: max-age=60

If you really want the browser to check for a new file with every single page load, you can still save some network traffic by using an ETag:

Cache-Control: max-age=0
Pragma: no-cache
Expires: Sat, 01 Jan 2000 00:00:00 GMT
ETag: "o2389r-98ur0-w3894tu-q894"

The ETag is simply a unique identifier your Web server makes up each time the file changes. The next time the browser wants the file, it asks the server, "does /js/pm.init.js still have the ETag o2389r98ur0w3894tuq894?" and if so, your server simply says, "yes". That way your server doesn't have to send the whole file again, and the user doesn't have to wait for it to load. Win-win.

How to convince your web server to autogenerate ETags depends on the server. It's usually not hard.

I've seen the hack you're using before. Like so much on the Web, it's not pretty or particularly efficient, but it works.

Jason Orendorff
  • 42,793
  • 6
  • 62
  • 96
  • "to tell browsers not to cache them:" - the example you provided has `no-cache`, I think you meant to use `no-store`, right? – David Klempfner Sep 02 '21 at 05:15
  • OK, I changed it. `no-cache` might be better depending on the exact problem, but `no-store` is the setting that forbids caching entirely. I hope there's less JS and CSS being generated on the fly these days! – Jason Orendorff Sep 02 '21 at 20:02
14

If The reason we want to prevent caching is to ensure the newer version of the files are loaded when we do a new release., you want that the new js is loaded when THERE IS a NEW release, not all the times.

In order to do that you want that the "ts" value is linked with the file not with the time of the day. You can either use one of these system:

  1. ts = Timestamp OF THE FILE
  2. ts = MD5 (or whatever checksum) of the FILE
  3. ts = version of the code. If you have a script that do deployment be sure that it adds the version code (or the date of the release) in some include file that will be assigned to ts.

In this way the browser will reload the file only if it is new and not all the times.

Chris Cinelli
  • 4,679
  • 4
  • 28
  • 40
3

A simple approach to this would be to use last modified date of the js or css files in the URL instead of a time stamp. This would have the effect of preventing caching only when there is a new version of the file on the server.

3

Edit

In case you are using Spring Boot it's now much simpler to prevent caching of modified files.

All you need to do is add this to application.properties:

spring.resources.chain.strategy.content.enabled=true
spring.resources.chain.strategy.content.paths=/**

If you are using Thymeleaf or FreeMarker it's completely autoconfigured. If you are using JSPs you need to manually declare a ResourceUrlEncodingFilter.

Read more here: http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-developing-web-applications.html#boot-features-spring-mvc-static-content

What follows now is my "old" post which also works but requires more work.


Since you are using Java there is a chance you are also using maven to manage your project.

In that case, to improve performance, and to make sure that no browser caches your static resources when a new release of your software is produced, you should combine all of your stylesheets and JavaScript files into on single file of their type, and you should make your resource urls change when you create a new release.

Luckily maven can do all this for you at build-time. You'll need minify-maven-plugin and maven-replacer-plugin.

This excerpt of a pom.xml should get you started:

<properties>
    <timestamp>${maven.build.timestamp}</timestamp>
    <maven.build.timestamp.format>yyyyMMddHHmm</maven.build.timestamp.format>
</properties>

<plugin>
    <groupId>com.samaxes.maven</groupId>
    <artifactId>minify-maven-plugin</artifactId>
    <version>1.6</version>
    <executions>
        <execution>
            <id>minify-css</id>
            <phase>process-resources</phase>
            <goals>
                <goal>minify</goal>
            </goals>
            <configuration>
                <linebreak>-1</linebreak>
                <cssSourceDir>resources/css</cssSourceDir>
                <cssSourceFiles>
                    <cssSourceFile>bootstrap.css</cssSourceFile>
                    <cssSourceFile>style.css</cssSourceFile>
                </cssSourceFiles>
                <cssTargetDir>resources/css</cssTargetDir>
                <cssFinalFile>${timestamp}.css</cssFinalFile>
            </configuration>
        </execution>
        <execution>
            <id>minify-js</id>
            <phase>process-resources</phase>
            <goals>
                <goal>minify</goal>
            </goals>
            <configuration>
                <linebreak>-1</linebreak>
                <jsSourceDir>resources/js</jsSourceDir>
                <jsSourceFiles>
                    <jsSourceFile>jquery.js</jsSourceFile>
                    <jsSourceFile>bootstrap.js</jsSourceFile>
                    <jsSourceFile>script.js</jsSourceFile>
                </jsSourceFiles>
                <jsTargetDir>resources/js</jsTargetDir>
                <jsFinalFile>${timestamp}.js</jsFinalFile>
            </configuration>
        </execution>
    </executions>
</plugin>

<plugin>
    <groupId>com.google.code.maven-replacer-plugin</groupId>
    <artifactId>replacer</artifactId>
    <version>1.5.2</version>
    <executions>
        <execution>
            <id>replaceDynPartInResourcePath</id>
            <phase>prepare-package</phase>
            <goals>
                <goal>replace</goal>
            </goals>
            <configuration>
                <ignoreMissingFile>false</ignoreMissingFile>
                <basedir>${project.build.directory}</basedir>
                <file>${project.artifactId}/WEB-INF/views/header.jsp</file>
                <regex>false</regex>
                <replacements>
                    <replacement>
                        <token>$dynamicResourceNamePart$</token>
                        <value>${timestamp}</value>
                    </replacement>
                </replacements>
            </configuration>
        </execution>
    </executions>
</plugin>

This is how to include your static resources in header.jsp

<c:choose>
    <c:when test="${not fn:contains(pageContext.request.serverName, 'localhost') and empty param.nocombine}">
        <link href="${pageContext.request.contextPath}/resources/css/$dynamicResourceNamePart$.min.css" rel="stylesheet" type="text/css" />
        <script src="${pageContext.request.contextPath}/resources/js/$dynamicResourceNamePart$.min.js" type="text/javascript"></script>
    </c:when>
    <c:otherwise>
        <link href="${pageContext.request.contextPath}/resources/css/bootstrap.css" rel="stylesheet">
        <link href="${pageContext.request.contextPath}/resources/css/style.css" rel="stylesheet">
        <script type="text/javascript" src="${pageContext.request.contextPath}/resources/js/jquery.js"></script>
        <script type="text/javascript" src="${pageContext.request.contextPath}/resources/js/bootstrap.js"></script>
        <script type="text/javascript" src="${pageContext.request.contextPath}/resources/js/script.js"></script>
    </c:otherwise>
</c:choose>
yglodt
  • 13,807
  • 14
  • 91
  • 127
  • Hi, @yglodt I just want to understand better. Right now in my every JSP file I am including my js and css files like "". But this is a bad way of implementation. So after I start using "spring.resources.chain.strategy.content.enabled=true", I can remove "java.util.Dae()" from my js and css files? – Vibhav Chaddha Dec 17 '19 at 09:28
  • 1
    @Me_developer: If you use Spring Boot, and also or tag for your js and css file src-tags, then yes. – yglodt Dec 17 '19 at 21:56
  • Yes I am using Spring Boot and jquery with JSP. So now I am using it like this "">" and the same for my css file like, "">". I hope I am not doing anything wrong? I have also manually declared 'addResourceHandlers' and 'ResourceUrlEncodingFilter' bean. Thanks. – Vibhav Chaddha Dec 19 '19 at 06:04
1

For debug purposes, install the web developer toolbar for FireFox and activate there "deactivate Cache"

Update:
When you have FireBug installed, you can disable caching as well in the network tab settings.

powtac
  • 40,542
  • 28
  • 115
  • 170
  • 1
    Do we have a similar kind of tool for Chrome? – Raghav Aug 05 '11 at 19:36
  • 2
    Google Chrome comes with the Chrome Developer Tools built into the browser. if you open up the Chrome Dev Tools, you will see the gear in the bottom right corner. Click on that and check the box for "Disable cache" under the "General" header. – Matt Jul 17 '12 at 19:35
0

If you can include Java Servlet Filter in your application, here is a working solution: CorrectBrowserCacheHandlerFilter.java

Basically, when your browser requests the static files, the server will redirect every requests to the same one but with a hash query parameter (?v=azErT for example) which depends on the content of the target static file.

Doing this, the browser will never cache the static files declared in your index.htmlfor example (because will always received a 302 Moved Temporarily), but will only cache the ones with the hash version (the server will answer 200 for them). So the browser cache will be used efficiently for those static files with hash version.

Disclaimer: I'm the author of CorrectBrowserCacheHandlerFilter.java.

Anthony O.
  • 22,041
  • 18
  • 107
  • 163
  • Is there a benefit that this method provides, that `Cache-Control: no-cache` does not provide? – David Klempfner Sep 02 '21 at 06:15
  • @DavidKlempfner because that way you can still benefit from cache if the target file does not change (but the browser will always issue 2 requests for each static file instead of one...). Moreover, sometime, `Cache-Control: no-cache` is not enough, see the replies of https://stackoverflow.com/q/49547/535203 – Anthony O. Sep 02 '21 at 08:46