Handling Image Caching with CakePHP (and Asset Compress) - cakephp

From Yahoo!'s Best Practices for Speeding Up Your Web Site document:
Expires headers are most often used with images, but they should be
used on all components including scripts, stylesheets, and Flash
components.
I follow the above advice using the "mod_expires" Apache module. My implementation is much like HTML5 Boilerplate's. See this .htaccess code.
Here is another quote from the same Yahoo! document:
Keep in mind, if you use a far future Expires header you have to
change the component's filename whenever the component changes. At
Yahoo! we often make this step part of the build process: a version
number is embedded in the component's filename, for example,
yahoo_2.0.6.js.
I have taken care of this with my CSS and JavaScript files using Mark Story's Asset Compress plugin. It's just a matter of making Asset Compress' shell part of the build process.
Now for the two issues I've run into, both related to images:
I have regular <img> tags throughout my websites and I also have CSS background-images. I currently don't have an elegant way of handling cache busting for either of those two types of images. For <img> tags, I have this line in my "core.php" file:
Configure::write('Asset.timestamp', 'force');
Although this does provide a way of automatically handling cache busting for <img> tags (provided the tags are generated using $this->Html->image(...)), I don't consider this to be elegant for two reasons:
It uses a query string, which is not recommended.
The image's timestamp is checked every time that particular view is accessed. Yes, you could cache the view, but you may want the image(s) in that view to be updated before the cached version of the view expires, so you would have to do whatever is needed to trigger that view to be re-cached, which I don't consider to be elegant.
As for handling the cache busting of CSS background-images, I have to manually update the LESS file. Definitely not elegant.
How is image caching supposed to be handled with CakePHP and/or Asset Compress?

Cache Invalidation, performance and the web
It's commonly held that one of the hardest things to do in programming is cache invalidation. However with assets (js files, css files, images etc.) that's not really true optimal logic for serving web assets is:
Serve with long cache expiry (1 year)
Don't use etags
If the asset changes change the url
There is however one complication when applied to the web.
Consider a request for /css/main.css, containing:
body {
background-image:url('../img/bar.gif');
}
This will, obviously, trigger a request for /img/bar.gif when the css file is loaded. Assuming the image is served with appropriate headers, there are only two ways to load an updated version of bar.gif:
change the contents of the css file
change the folder where the css file is
The first is problematic if it's not automated (and even if automated, could go wrong) the second is easy.
Version-prefix asset urls -> never have problems again
One simple way to solve the css/js/files problem is to make your build number part of the url:
/v123/css/foo.css
^
You can do this by modifying your app helper webroot function for example:
public function webroot($file) {
$file = parent::webroot($file);
if (Configure::read('debug')) {
return $file;
}
return '/' . Configure::read('App.version') . $file;
}
Incidentally it's the same technique to use a cdn - probably the best thing you can do to improve frontend performance.
In this way when you bump your site version - all assets get new urls. Note that using this technique, all referenced assets need to use relative urls, not absolute:
.foo {
/* background-image:url('/img/bar.gif'); // won't work */
background-image:url('../img/bar.gif');
}
Otherwise the request for the css file is application-version specific but the referenced image is not and would be read from browser cache (if relevant) even with a new application version.
Achieving the same result, no changes to file system
You can use a rewrite rule similar to the one in h5bp's for filename cache-busting if you don't want to change your folder structure:
<IfModule mod_rewrite.c>
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^/v\d+/(css|files|js)/(.+)$ /$1/$2 [L]
</IfModule>
This will mean that the url /v123/css/main.css serves the contents of /css/main.css at the time of the request.
Parsing css files is complex
You mention in a comment
I think the fact that changing one asset causes all assets to be re-downloaded is a deal-breaker
Unless you are releasing a new production version every minute - it's not going to be a problem (unless you have GB of cached content in which case .. you have different problems). The only way to have performant cache logic that is file specific is to e.g. store each file in your site as the sha1 of the file's contents - applied to css files that means replacing ../img/foo.gif with ../img/<hash of foo.gif's contents>.gif.
There's nothing to stop using multiple techniques, for example with the following structure:
app
webroot
css
img <- css assets only
fonts
img
js
You can version-prefix your css, fonts and js requests; indirectly do the same for css-images (assuming they use relative urls of the form background-image:url('img/bar.gif');) without applying the same logic to other assets (user avatars, their uploaded cat videos, whatever).
Or use data uris for all images
It's what google does =).
At the end of the day it becomes a choice between how complex you want your build process to be, and for what real benefit. Many users have empty browser caches, so it's quite likely that for a random user the cache logic of an application will only apply to their current visit - one of the main reasons why expiring your whole asset cache in one go isn't such a bad thing.

Related

What is the reason behind having data URI instead of path for images less than 10kb in React?

In the Create React App documentation inside the Adding Images, Fonts, and Files section, they say :
importing images that
are less than 10,000 bytes returns a data URI instead of a path. This
applies to the following file extensions: bmp, gif, jpg, jpeg, and
png.
The reason for them is :
To reduce the number of requests to the server
But is it specific to how React works (updating the DOM for example) or it's a wide spread practice in web development in order to reduce load times?
This is not a practice that's particular to React. Whether something gets rendered via React or by ordinary HTML, if an image is rendered using a data URI, if the data URI exists in the code already (either bundled inside the JS or hard-coded into the HTML), no additional requests to the server will have to be made.
In contrast, if you have something like src="theImage.png", that'll result in a request to the server to download the image.
it's a wide spread practice in web development in order to reduce load times?
Yes.
If, for example, the web server was using HTTP 1.1, and you had, say, 25 images with srcs pointing to different files, the sheer number of separate requests could present a problem - regardless of whether React was being used or not.
(HTTP/2 mitigates this problem at least somewhat - see here)

Using image urls vs importing images locally

Is it generally better performing to use an image by referencing it with an url or by downloading it, saving it to some directory, and then importing it in a component?
Eg
<img src="images.google.com/example.png" />
vs
import Example from './static/example.png';
<img src={Example} />
Since React doesn't have any means of loading or serving image data, this question is inherently about other tooling, like Webpack.
Storing images locally and loading them via webpack has the benefit of being able to process them with your Webpack loaders, which enables things like inlining them (via base64 resource URLs) when they're small enough and fingerprinting them. You'll still want to deploy those final assets somewhere that gets wrapped in a CDN, though.
Referencing remote images via string URL alone has the advantage of not having sizeable binary images bloating your SCM repository, not needing to import each image explicitly (remember, there are no glob imports in Webpack), and reducing your webpack build times.
I suspect most React apps are going to end up using both, usually in different use cases. I always use local images for logos and app assets for example, whereas I'll reference external files for any user-uploaded content or larger JPG/PNG assets.
CDNs are usually preferred for delivering static files such as images. Due to the fact that the Google Cloud CDN caches your files through its edge servers, your visitors will have their static content delivered to them from the closest point of presence instead of originating from the same server your application is running from.
Having your static content served and cached in multiple geographical locations around the world ensures an optimal content delivery time.
Developing locally however, you may notice that having your static content locally is faster. This is normal, because you're fetching an image over the internet vs from your local hard drive. However, once your app will be deployed and running (say from Canada), visitor A who is from Eastern Europe may be experiencing slower delivery times than visitor B who is from the States. Because you are serving a global customer base from one geographical location, your visitors from the a completely different timezone will receive an unfair treatment in the time it takes for their content to be delivered.
In another perspective, why not both? Have your application serve static content from a CDN but also have your static content on the same application folder and gracefully fall to the local one if the CDN fails.
TL;DR
Serve your images from a CDN ( url ) if you have a large customer base for optimization.

ReactJS + Express Cache Busting issue

I'm trying to avoid cache busting by setting version numbers in the index.html file name (index.hash.html) generated with html-webpack-plugin. However, I'm unable to get the browser to grab the new file from the server because the old index.html file is still cached for X amount of time.
I could clear cache to hit the server again, or change the cache-control header but this doesn't really work well for users that already have the file cached since it seems they won't see the changes to cache-control anyway. I'm looking for the correct solution and can't seem to find one for this issue.
Any suggestions?
I'm no expert on this, but we're using no hashing for index.html. This means the TTL for it is zero.
On the other hand, all other assets ( js, css , svg ... ) have hashes defined and are cached. Our Service worker on the client checks for newer versions and serve them accordingly.
Hope this helps!

Grails Asset Pipeline: URLs within static assets

In Grails Asset Pipeline, I am serving HTML, js, CSS, and image files.
In an HTML asset, if I need to supply a src for an image, how should I write that URL to take into account:
that assets might be served from a different base URL, as specified by grails.assets.url
that assets might be served from the Grails server (in dev mode), but under a potentially variable app context
I can think of the following solutions:
always use relative paths between assets.
The problem with this is that if I ever move an asset, then all of its relative links must change.
Another possible issue would be that, if I somehow made Asset Pipeline route proprietary assets to my own static server, and thrid-party assets to public CDNs (e.g., https://ajax.googleapis.com/ajax/libs for angularjs, jquery, etc.), then any static relative URLs wouldn't work.
I assume that I shouldn't ever directly reference js and/or css files outside of Asset Pipeline manifests, so, unless that's wrong, then this problem shouldn't occur for those file types, but, if there's a CDN for common images (does such a thing exist?), then static relative URLs in html files wouldn't work in img src attributes.
angularjs & a javascript variable set via a gsp
Use angularjs in the html file to read a javascript variable that contains the base URL. Set the base URL in a gsp that is referenced by every page.
The problems with this are:
that a separate request for the gsp is necessary (though it should be able to be cached for a long time). Is it possible to compile a gsp at build-time into a js file, and copy the result as an asset, so that it could be included in the Asset Pipeline static bundle rather than served from my Grails server?
that browser cycles are used processing the angularjs code
that certain third-party javascript libraries don't play well with angularjs, so it might be complex to get them working with this setup
from what I know, url() calls in css would still have to be relative, since angularjs wouldn't be able to influence them. Maybe I could use one of the css wrapper languages, like less, but this option is getting much more complicated than option 1...
Are there any other viable options?
Are there any other cons and/or gotchas to either option that I've mentioned above?
I'm using the current version of Asset Pipeline (2.2.5), and the latest version of Grails 2.x (2.5.0).

Linking to assets on a cdn with cakephp

I am getting ready to deploy a cakephp app onto the web and i want to move all the assets (img, js, css) to a CDN to increase performance. Is there a way to globally change the location the HTML helper links to assets instead of having to change every link.
Recently I came across this cool helper that accomplishes this task with relative ease. It's called Asset Host Helper and can be obtained from its GitHub repository.
What I liked best about it is that you don't need to worry about changing the location of the assets in your development copy (most likely on localhost) or in your production copy (on the CDN). The helper takes care of it automatically.
Check it out - this might just be the tool you're looking for.
Cheers,
m^e
If the routes and filenames persist, maybe mod_rewrite might be less painful.
RewriteCond %{REQUEST_URI} ^/css/
RewriteRule ^css/(.*)$ http://cd.yourdomain.com/css/$1 [R=301,L]
I had a similar problem, here's how I solved it:
Adding a prefix to every URL in CakePHP
The AppHelper::url() method is the place you should be interested in.
I have a solution but it involves changing the core, I know I know...I have already slapped myself for doing it ;-)
We had a project that was built and then needed a CDN so we just added a bit of code to the HTML and Javascript helpers to assist us.
In the /cake/libs/view/helpers/html.php file add this at line 360
if (Configure::read('Asset.CDN.enabled')) {
$static_servers = Configure::read('Asset.CDN.static_servers');
if(sizeof($static_servers) > 0) {
shuffle($static_servers);
$url = $static_servers[0].$url;
}
}
and in /cake/libs/view/helpers/javascript.php ass this at line 288
if (Configure::read('Asset.CDN.enabled')) {
$static_servers = Configure::read('Asset.CDN.static_servers');
if(sizeof($static_servers) > 0) {
shuffle($static_servers);
$url = $static_servers[0].$url;
}
}
Then in your app/config.core.php file just add the following configuration options
// Static File Serving on a CDN
Configure::write('Asset.CDN.enabled', false);
Configure::write('Asset.CDN.static_servers', array('http://static0.yoursite.com.au/', 'http://static1.yoursite.com.au/'));
Now when you refresh your page each file that is outputted through the html/javascript helper will automatically pick a random static server.
Note that unless you are using absolute paths (including domain names) in your css files you will need to make sure the images are also on the static server.
I know you shouldn't really play around in the core but sometimes it is really just easier.
Cheers,
Dean
I know this is an old question but in case any future people stumble across it in rails 3.1 you can now use
config.action_controller.asset_host = "ATBTracking"
in config/environments/production

Resources