Versioning resources in Spring MVC with Thymeleaf

https://i.sstatic.net/ArllV.png

Struggling with resource versioning in Spring Mvc 4 while using thymeleaf template engine. Despite the code provided, I am unable to see the new version URL when viewing the page source. What could be causing this issue? What am I overlooking?

@Override
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/static/theme*//**").addResourceLocations("/resources/static/theme/")
            .setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS))
            .resourceChain(false)
            .addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"))
            .addTransformer(new CssLinkResourceTransformer());
    registry.addResourceHandler("/static*//**").addResourceLocations("/resources/static/")
            .setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS))
            .resourceChain(false)
            .addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"))
            .addTransformer(new CssLinkResourceTransformer());
    registry.addResourceHandler("/static/js*//**").addResourceLocations("/resources/static/js/")
            .setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS))
            .resourceChain(false)
            .addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"))
            .addTransformer(new CssLinkResourceTransformer());
}

@Bean
public ResourceUrlEncodingFilter resourceUrlEncodingFilter() {
    return new ResourceUrlEncodingFilter();
}

Utilizing expressions in the script tag. th:src="@{/resources/static/js/companyList.js}"

Answer №1

By making changes only in the application.properties file, I successfully implemented it without writing any code:

# Enabling HTML5 application cache manifest rewriting.
spring.resources.chain.html-application-cache=true

# Activating the Spring Resource Handling chain. This is turned off by default unless a strategy has been enabled.
spring.resources.chain.enabled=true
# Utilizing the content Version Strategy.
spring.resources.chain.strategy.content.enabled=true
# List of patterns to be applied to the Version Strategy.
spring.resources.chain.strategy.content.paths=/**

I achieved adding hash versions to URLs for CSS and JS without needing to add any additional code.

Answer №2

1. Develop a custom Thymeleaf LinkBuilder using Spring's ResourceUrlProvider to generate versioned links:

@Configuration
public class TemplateEngineConfig {
    @Autowired
    public void configureTemplateEngine(SpringTemplateEngine engine,
                                        ResourceUrlProvider urlProvider) {
        engine.setLinkBuilder(new VersioningLinkBuilder(urlProvider));
    }
}

class VersioningLinkBuilder extends StandardLinkBuilder {
    private final ResourceUrlProvider urlProvider;

    VersioningLinkBuilder(ResourceUrlProvider urlProvider) {
        this.urlProvider = urlProvider;
    }

    @Override
    public String processLink(IExpressionContext context, String link) {
        String lookedUpLink = urlProvider.getForLookupPath(link);
        if (lookedUpLink != null) {
            return super.processLink(context, lookedUpLink);
        } else {
            return super.processLink(context, link);
        }
    }
}

2. Implement the usage of thymeleaf tags th:href and th:src

<link th:href="@{/main.css}" rel="stylesheet" type="text/css"/>
<script th:src="@{/js/main.js}" type="text/javascript"></script>

This will result in:

<link href="/main-0c362e5c8643b75ddf64940262b219f7.css" rel="stylesheet" type="text/css"/>
<script src="/js/main-c13acb86fa1012e27bbb01a7c4a9bf7f.js" type="text/javascript"></script>

3.(Optional) It is advisable to include browser cache headers. Add the following to your application.properties:

spring.resources.chain.strategy.content.enabled=true
spring.resources.chain.strategy.content.paths=/**
spring.resources.cache.cachecontrol.max-age=365d
spring.resources.cache.cachecontrol.no-cache=false
spring.resources.cache.cachecontrol.no-store=false
spring.resources.cache.cachecontrol.cache-public=true

If you prefer using application.yml:

spring:
  resources:
    chain:
      strategy:
        content:
          enabled: true
          paths: /**
    cache:
      cachecontrol:
        max-age: 365d
        no-cache: false
        no-store: false
        cache-public: true

Answer №3

Here is what worked for me:

Update in application.yml

...
resources:
  chain:
    strategy:
      content:
        enabled: true
        paths: /js/**,/css/**
...

Adjustment in index.html

...
<script th:src=@{/js/home.js}></script>
...

Outcome

The result will look something similar to this:

...
<script src=/js/home-440273f30b71d3cf4184b48ce5e10b94.js></script>
...

Answer №4

In solving the issue, I analyzed the source code of Spring's ServletContextResource class to create a relative path and checked for the existence of the resource.

Resource location: /resources/static/

Path: /static/css/login.css

pathToUse: /resources/static/static/css/login.css --> this resource URL does not exist so it returns null.

ServletContextResource Class:

@Override
public Resource createRelative(String relativePath) {
    String pathToUse = StringUtils.applyRelativePath(this.path, relativePath);
    return new ServletContextResource(this.servletContext, pathToUse);
}

Solution: Resource location: /resources/static/

Path: /css/login.css

pathToUse: /resources/static/css/login.css

I updated the format by removing /resources from the path.

th:src="@{/css/login.css}"

@Override
public void addResourceHandlers(final ResourceHandlerRegistry registry) 
{

     registry.addResourceHandler("/theme*//**").addResourceLocations("/resources/static/")
             .setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS))
             .resourceChain(false)
             .addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"))
             .addTransformer(new CssLinkResourceTransformer());
     registry.addResourceHandler("/css*//**").addResourceLocations("/resources/static/")
             .setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS))
             .resourceChain(false)
             .addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"))
             .addTransformer(new CssLinkResourceTransformer());
     registry.addResourceHandler("/js*//**").addResourceLocations("/resources/static/")
             .setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS))
             .resourceChain(false)
             .addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"))
             .addTransformer(new CssLinkResourceTransformer());

  @Override
  public void configure(final WebSecurity web) throws Exception {
web.ignoring().antMatchers("/theme/**").antMatchers("/js/**").antMatchers("/css/**");
         }

Answer №5

The ResourceUrlEncodingFilter is being used to filter all URLs on the page, but this can lead to performance issues and is not an ideal solution. To address this issue, I recommend the following:

registry.addResourceHandler("/javascript/*.js", "/css/*.css", "/img/*")
                    .addResourceLocations("classpath:/static/javascript/", "classpath:/static/css/", "classpath:/static/img/")
                    .setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS))
                    .resourceChain(true)
                    .addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"));

To reference static resources within the page, you can use the function below:

<script th:src="${@mvcResourceUrlProvider.getForLookupPath('/javascript/app.js')}"></script>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the best way to empty a backbone collection in preparation for loading new data?

Hey everyone, I've been working on a Backbone application that involves adding and deleting or editing images. Currently, I'm using a router to navigate between different sections like the gallery and forms. However, whenever I make changes in th ...

Angular integration of Jquery UI datapicker with read-only feature

Having trouble using ngReadonly within a directive, my code isn't functioning as expected: app.directive('jqdatepicker', function() { return { restrict: 'A', require : 'ngModel', link : functi ...

Generate a dynamic text-box and drop-down list using HTML and JavaScript

My current project involves generating dynamic text-boxes based on the selection from a drop-down list and input field. https://i.sstatic.net/CMtW9.png When a user chooses 'Option 1' from the drop-down list and enters input such as 'grass& ...

How can I implement a hover-over image change effect similar to the one seen in Gmail accounts?

I am attempting to implement a feature that allows users to change their image, similar to what can be done in a GMail account. When the user hovers over the image, an option to "change image" should appear. This may be a new concept for me because I am ...

Elements powered by jQuery failing to load upon dynamic webpage(s) loading via ajax

Dynamic loading of jQuery elements, such as Ibuttons, seems to be causing issues when implemented with an ajax dynamic content loader. The entirety of my website is rendered through Ajax just like this: <html> <header> {jQuery} </header> ...

Discovering the power of Next.js Dynamic Import for handling multiple exportsI hope this

When it comes to dynamic imports, Next.js suggests using the following syntax: const DynamicComponent = dynamic(() => import('../components/hello')) However, I prefer to import all exports from a file like this: import * as SectionComponents ...

Select multiple rows by checking the checkboxes and select a single row by clicking on it in the MUI DataGrid

I am currently utilizing the MUI DataGrid version 4 component. The desired functionalities are as follows: Allow multiple selections from the checkbox in the Data Grid (if the user selects multiple rows using the checkbox). Prevent multiple selections fr ...

Determine the duration/length of an audio file that has been uploaded to a React application

I am working on a React web application built with create-react-app that allows users to upload songs using react-hook-forms. The uploaded songs are then sent to my Node/Express server via axios. I want to implement a feature that calculates the length of ...

I am endeavoring to send out multiple data requests using a function, ensuring that the code execution occurs only once all the results have been received

After reading through a post on Stack Overflow about handling multiple AJAX calls in a loop (How to take an action when all ajax calls in each loop success?), I was able to come up with a solution. I decided to track the number of requests I needed to make ...

Is it possible for URLSearchParams to retrieve parameters in a case-insensitive manner

Is it possible for URLSearchParams to search a parameter without being case-sensitive? For instance, if the query is ?someParam=paramValue, and I use URLSearchParams.get("someparam"), will it return paramValue? ...

Autocomplete fails to recognize any modifications made to the original object

I am currently utilizing the jQuery library's autocomplete() method on a text input field, setting Object.getOwnPropertyNames(projects) as the source: $(function() { $("#project").autocomplete({source: Object.getOwnPropertyNames(projects)}); } B ...

Using ASP.NET MVC 6 Web API along with Angular, a resource can be posted as an object property in the

I am facing a challenge in sending a complex object from Angular to my web API. Here is how the object looks: { Name: "test", Tax: 23, Addresses: [ { country: "ro", city: "bucharest" }, { country: "fr", ...

Strategies for efficiently loading 100,000 records into a table using Spring Boot on the server side and Angular on the front end

I am facing an issue with the speed of loading data from the back end to the front end without causing delays on the client side. I am using Spring Boot for the back end and Angular 7 for the front end. The problem arises when I submit a request from the f ...

Executing a JavaScript function to trigger a PHP script that saves data into a MySQL database

I have a button in my HTML file that, when clicked, should insert data into a database and then reload the page. I am calling a JavaScript function within the onclick event to handle this. Below is the JavaScript function: function grandFinale() { i ...

Using Selenium WebDriver to Extract Variables from JavaScript SourceCode

Currently, I am dealing with a web page source code that utilizes JavaScript to display text. Below is an excerpt from the source: var display_session = get_cookie("LastMRH_Session"); if(null != display_session) { document.getElementById("sessionDIV") ...

How to eliminate excess white space on the right side in Bootstrap 4

Could someone please clarify why I am experiencing a white space on the right side when using Bootstrap 4? This is the primary code block for the page. <main id="intro" role="intro" class="inner text-center"> <h2>Lorem Ipsum</h2> ...

I'm looking to add a price ticker to my html webpage. Does anyone know how to do this?

PHP Code <?php $url = "https://www.bitstamp.net/api/ticker/"; $fgc = file_get_contents($url); $json = json_decode($fgc, true); $price = $json["last"]; $high = $json["high"]; $low = $json["low"]; $date = date("m-d-Y - h:i:sa"); $open = $json["open"]; ...

Tips for implementing jQuery on HTML loaded post document.ready():

I've encountered a scenario where I have multiple HTML elements being rendered by a JavaScript function on the page: <a href="#" class="test1">Test</a> <a href="#" class="test2">Test</a> <a href="#" class="test3">Test< ...

The error you are seeing is a result of your application code and not generated by Cypress

I attempted to test the following simple code snippet: type Website = string; it('loads examples', () => { const website: Website = 'https://www.ebay.com/'; cy.visit(website); cy.get('input[type="text"]').type(& ...

"Protractor encountered an issue when navigating to the most up-to-date Angular section in our

We are in the process of upgrading our application from AngularJS to the latest version of Angular. I am currently working on writing tests that transition from the AngularJS version of the app to the admin application, which is built using the latest ver ...