Skip to content

Auto-Versioning JavaScript and CSS Files in PHP

Two methods to automatically version JavaScript and CSS files in plain PHP

How often have your clients complained to you about not being able to see the new changes you have made to their website? This happens to me a lot, and it seems to be quite a common occurrence for other web developers too. For example, perhaps I’ve just updated the JavaScript and/or CSS files, but my client’s browser is still using their cached version (preventing them from seeing any of the new changes) and they’re confused as to why they’re looking at an older site.

The most common way to tackle this issue is to version JavaScript and CSS files. Unfortunately for me, however, manually versioning all the files every time that I have to perform a deploy is boring and error-prone. Happily, there is a way around this that this article will dive into, so without further ado let’s explore two ways to automatically version JavaScript and CSS Files in PHP.

Please note: While we are going to look at how to auto-version JavaScript and CSS files, the two following methods could easily be adapted to images (or any other files) as well.

Method 1

This method aims at harnessing the modification time of a JavaScript or CSS file to implement auto-versioning. The idea is to automatically append a query string to each of our files. This query string represents their own version, whose number is determined from their modification time. In so doing, we are tricking the browser into thinking that new files are being specified when, in fact, browsers usually simply look at the full name of each file (meaning that they will be cached accordingly).

This can be easily achieved with some lines of code, as follows:

<?php
  
/**
 *  Given a valid file location (it must be an path starting with "/"), i.e. "/css/style.css",
 *  it returns a string containing the file's mtime as query string, i.e. "/css/style.css?v=0123456789".
 *  Otherwise, it returns the file location.
 *
 *  @param $file  the file to be loaded.
 */
function auto_version($file) {
    // if it is not a valid path (example: a CDN url)
    if (strpos($file, '/') !== 0 || !file_exists($_SERVER['DOCUMENT_ROOT'] . $file)) return $file;

    // retrieving the file modification time
    // https://www.php.net/manual/en/function.filemtime.php
    $mtime = filemtime($_SERVER['DOCUMENT_ROOT'] . $file);

    return sprintf("%s?v=%d", $file, $mtime);
}
<?php

const CSS = array(
	'/css/myCSSFile1.css',
	'/css/myCSSFile2.css',
	'/css/myCSSFile3.css',
);

foreach (CSS as $css)	
  echo '<link rel="stylesheet" href="' . auto_version($css) . '" type="text/css">';
<?php

const JS = array(
	'/js/myJsFile1.js',
	'/js/myJsFile2.js',
	'/js/myJsFile3.js',
);

foreach (JS as $js)	
	echo '<script src="' . auto_version($js) . '" type="text/javascript"></script>';

By inspecting the HTML code of the web page, we will see:

<link rel="stylesheet" href="/css/myCSSFile1.css?v=1593800287" type="text/css">
<link rel="stylesheet" href="/css/myCSSFile2.css?v=1693189689" type="text/css">
<link rel="stylesheet" href="/css/myCSSFile3.css?v=1933020647" type="text/css">
<script src="/js/myJsFile1.js?v=1752818290" type="text/javascript"></script>
<script src="/js/myJsFile2.js?v=1912415271" type="text/javascript"></script>
<script src="/js/myJsFile3.js?v=1452837239" type="text/javascript"></script>

The downside of this method is that some browsers may ignore the appended query strings and use the previously cached copy, instead of the new version with the same files. This is why we are going to run through a second method.

Method 2

This method is more complex and aims to automatically append the version of each JavaScript and CSS file directly to their name. Again, the version number is generated using the file modification time. Firstly, we need to add some additional lines to our .htaccess file, since some mod_rewrite rules to allow version numbers in our file names are required:

RewriteEngine on
RewriteRule ^(.*)\.[\d]{10}\.(css|js)$ $1.$2 [L]

Apache will now automatically redirect any files with 10 digits (since 10 digits cover all timestamps from 9/9/2001 to 11/20/2286) before a .css or .js extension back to just the filename and extension. With these rules in place, the URL .../style.css can now be rewritten as .../style.0123456789.css and Apache will see those as the exact same files. Additionally, as the second file has a different name from the first one, the browser will ignore the previously cached copy of the file.

Now, we need to change our CSS and JavaScript import logic. This can be easily achieved with a custom PHP function:

<?php

/**
 *  Given a valid file location (it must be an path starting with "/"), i.e. "/css/style.css",
 *  it returns a string containing the file's mtime, i.e. "/css/style.0123456789.css".
 *  Otherwise, it returns the file location.
 *
 *  @param $file  the file to be loaded.
 */
function auto_version($file) {
    // if it is not a valid path (example: a CDN url)
    if (strpos($file, '/') !== 0 || !file_exists($_SERVER['DOCUMENT_ROOT'] . $file)) return $file;

    // retrieving the file modification time
    // https://www.php.net/manual/en/function.filemtime.php
    $mtime = filemtime($_SERVER['DOCUMENT_ROOT'] . $file);

    return preg_replace('{\\.([^./]+)$}', ".$mtime.\$1", $file);
}
<?php

const CSS = array(
	'/css/myCSSFile1.css',
	'/css/myCSSFile2.css',
	'/css/myCSSFile3.css',
);

foreach (CSS as $css)	
  echo '<link rel="stylesheet" href="' . auto_version($css) . '" type="text/css">';
<?php

const JS = array(
	'/js/myJsFile1.js',
	'/js/myJsFile2.js',
	'/js/myJsFile3.js',
);

foreach (JS as $js)	
	echo '<script src="' . auto_version($js) . '" type="text/javascript"></script>';

By inspecting the HTML code of the web page, we will see:

<link rel="stylesheet" href="/css/myCSSFile1.1593800287.css" type="text/css">
<link rel="stylesheet" href="/css/myCSSFile2.1693189689.css" type="text/css">
<link rel="stylesheet" href="/css/myCSSFile3.1933020647.css" type="text/css">
<script src="/js/myJsFile1.1752818290.js" type="text/javascript"></script>
<script src="/js/myJsFile2.1912415271.js" type="text/javascript"></script>
<script src="/js/myJsFile3.1452837239.js" type="text/javascript"></script>

Conclusion

Not being able to view new updates to websites can be a confusing experience for clients, not to mention frustrating for web developers! There is a way to overcome this issue, however, as my article has explored. Using the above methods, the browser will be able to cache our JavaScript and CSS files, but when any changes are made it will not use their cached copies (meaning that we will not run into issues with the clients being unable to view new changes to the site).

I hope that you found this article helpful, thanks for reading!

nv-author-image

Antonello Zanini

I'm a software engineer, but I prefer to call myself a Technology Bishop. Spreading knowledge through writing is my mission.View Author posts

Want technical content like this in your blog?