Matt Brundage

Stale cache mitigation with query string automation!

So you’ve been a good little developer and define expires headers for page assets such as CSS, JS, and images. Let’s say you specify that the caching of CSS files expires one week after initial access. But if you modify a CSS file, your visitors could potentially load stale cache for up to one week.

One solution is to rename the file. For example, main.css would become main.2011-08-12.css This will effectively create a unique cached version of the CSS file. But this solution could get cumbersome with frequent updates, or with disparate references to the asset. A second solution is to add a query string to any references to the asset, for instance, main.css?2011-08-12. Proxy servers will treat this reference as a dynamic file and will likely not cache it. Browsers will treat this as if it were a unique file and re-cache it.

A refinement to our second solution is to automatically add a query string to references to the asset, but only when it’s necessary to do so. The following function, assetQueryString(), does just that. It takes two arguments:

  1. The reference to the page asset
  2. (optional) Maximum file age (in days) in which the query string will be appended

The function determines the file modification date and determines whether or not to append a query string, and for how long. The value of the query string, conveniently, is the last modification date of the file. Or, more accurately, it’s the number of days between the Unix Epoch and the last modification date.

PHP

function assetQueryString ($filePath, $maxAge = 7) {
	$today = intval(strtotime(date("Y-m-d")) / 86400);
	$fileDate = intval(strtotime(date("Y-m-d", filemtime($_SERVER['DOCUMENT_ROOT'].$filePath))) / 86400);
	$days = $today - $fileDate;

	if ($days <= $maxAge) {
		$filePath .= "?".$fileDate;
	}
	echo $filePath;
}

// usage:
<link rel="stylesheet" href="<?php assetQueryString("/templates/style/main.css"); ?>">

ColdFusion

<cffunction name="assetQueryString" returntype="string" output="FALSE">
	<cfargument name="filePath" type="string" required="TRUE" />
	<cfargument name="maxAge" type="numeric" default="7" required="FALSE" />

	<cfparam name="Variables.unixFileDate" default="0" />
	<cfset Variables.expandedFilePath = expandPath(filePath) />
	<cfset Variables.unixEpoch = CreateDate(1970,1,1) />

	<cfif fileExists(Variables.expandedFilePath)>
		<!--- determine last modified date --->
		<cfset Variables.unixFileDate = DateDiff("d", Variables.unixEpoch, getFileInfo(Variables.expandedFilePath).lastmodified) />

		<!--- determine today's date --->
		<cfset Variables.unixTodaysDate = DateDiff("d", Variables.unixEpoch, Now()) />

		<!--- append unique query string to file path if the file was recently modified --->
		<cfif Variables.unixTodaysDate - Variables.unixFileDate LTE maxAge>
			<cfset filepath &= "?" & Variables.unixFileDate />
		</cfif>
	</cfif>

	<cfreturn filepath />
</cffunction>

<!--- usage: --->
<link rel="stylesheet" href="<cfoutput>#assetQueryString("/templates/style/main.css")#</cfoutput>">

Comments are closed.