Tuesday, September 11, 2018

Spring Boot 2 + Thymeleaf - Versioning static assets for client side cache busting

In a post written some time ago, I showed how you can prevent caching of static resources (javascript and css) in a legacy Spring MVC application. Recently I needed to use the same technique to prevent browser side caching of javascript and css files in a Spring Boot 2 project using Thymeleaf as the view layer.

Purpose of this blog post


This blog post is about sharing a simple way to make your Spring Boot application serve versioned static resources like javascript and css files so that client side caching can be overcome when your static resources change.
Typically browsers download javascript and css files and then refer to them from the local cache. This is okay as long as you do not change the contents of your files. If you do make changes but you keep the same name then the browser will not know about the changes and use the same version from its local cache. Some approaches to defeat the cache are to add add a distinguishing query parameter (like myfile.css?v=1.1) or placing the files in a new subdirectory (like /js/v2/myfile.js) and then updating all the places in the HTML files where these are included. This is tedious and error prone to manually update the version query parameter or rename the file location in all the pages that it is included.

Fortunately Spring allows us to take care of this with minimal effort. Let us see how.

What is used to build the example in this blog post?

This example applies to a Spring Boot 2 application with Thymeleaf as the view layer. I am assuming that you have a standard Spring Boot 2 app with Thymeleaf. I am sure that this can be adapted to any other view technologies barring one as mentioned in the note below.

Before you start, I recommend you refer to the official documentation about serving static resources for further clarity.

Please note that if you are using Spring WebFlux then you cannot use this approach. See the official documentation here for more details.

Step 1: Adding entries to your application.properties (or application.yml) file

You need to add the following three entries in your application.properties file. What these mean is explained very clearly on the Spring documentation here in the Spring Resources Handling section.

File: application.properties


#########################################################
#Adding ResourceUrlEncodingFilter i.e. to version static 
#js and css files to prevent caching by browser
#########################################################

#Whether to enable the Spring Resource Handling chain. 
#By default, disabled unless at least one strategy has 
#been enabled.
spring.resources.chain.enabled=true

# Whether to enable the content Version Strategy.
spring.resources.chain.strategy.content.enabled=true

#Comma-separated list of patterns to apply to the content 
#Version Strategy i.e. where are your js and css files
spring.resources.chain.strategy.content.paths=/js/**,/css/**


Step 2: Define a ResourceUrlEncodingFilter bean

You need to create a ResourceUrlEncodingFilter bean and register it in your application's filter chain. With Spring Boot, it is very simple. Just create a MVC configuration and instantiate the bean there. For example, the following is all you need.

File: CacheBusterMVCConfig.java


@Component
public class CacheBusterMVCConfig implements WebMvcConfigurer {

    /**
     * Define the ResourceUrlEncodingFilter as a bean. 
     * Add the <code>@ConditionalOnEnabledResourceChain</code> 
     * to ensure that the resource chain property 
     * <code>spring.resources.chain.enabled=true</code> 
     * is set to true in the <code>application.properties</code> file.
     * 
     * @return
     */
    @Bean
    @ConditionalOnEnabledResourceChain
    public ResourceUrlEncodingFilter resourceUrlEncodingFilter() {
        return new ResourceUrlEncodingFilter();
    }
}


Results

If you download and run the project, you should see the screen as shown below.


Resources

The example project has been uploaded to Github here. If you like it, please star my repo.

Happy coding :)