An alternative solution to using an image Content Delivery Network
Ever since the release of the Mosaic browser in 1993, web browsers have had the ability to display web pages containing both images and text. Almost immediately, this functionality was harnessed to make pages stand out and add personality.
However, with the latest modems in 1993 being capable of around 19 kB/s, people quickly realized that image-heavy pages could be both a blessing and a curse. The number of bytes required to store an image was often reduced as much as possible by keeping it small, limiting colors, and applying aggressive lossy compression algorithms.
Why is image optimization important?
Today, we generally have much faster connections. However, image optimization remains a key mechanism to allow websites to stand out by enabling a better and richer experience for a wider range of users. Page load speed is more important than ever. Studies from major infrastructure providers such as Cloudflare show time and again that longer page loading time is directly correlated to higher bounce rates and lower sales conversion rates.
As images can account for a significant percentage of a page's total payload, reducing the size of those images is often one of the easiest ways to significantly improve page load times without changing the design of the site.
Having faster load times also ties into Search Engine Optimization (SEO). Google has adjusted search results based on page speed since at least 2010. More recently this has been explicitly extended to other areas such as mobile and ads. So, if your website is faster, not only are users more likely to stay and become customers, they are also more likely to find it in the first place!
Modern websites also have multiple types of devices to consider. Smartphones will have slower connections and smaller screens than desktops, so it makes sense to serve a smaller image. Different screen ratios may even mean that a cropped version of the image would be preferred. In addition, different browsers support different optimal image formats, with Chrome supporting WebP and Safari supporting JPEG 2000.
For the web site developer, identifying and supporting all these scenarios in order to deliver that optimal experience can seem overwhelming.
The rise of image CDNs
A CDN (Content Delivery Network) provides multiple locations around the world for the storage of static or semi-static data. The aim is to ensure that the relevant resources will always be geographically close to the user. This results in better performance and improves reliability while reducing traffic across the internet and distributing the load across multiple servers.
Image CDNs take this one step further by adding a layer that aims to automatically optimize the requested image for the user's device. This means that the website developer does not have to worry about any of the details of managing multiple formats and different versions of each image for different resolutions.
There are many different image CDN providers available and most do an excellent job in terms of serving optimized images to users on-demand. However, there are downsides. Maintaining an image CDN is undoubtedly simpler and less prone to error than handling image optimization in-house, but in most cases, these CDNs do still require maintenance.
In addition, the cost of most image CDN solutions is significant. Clearly, the success of image CDNs shows that the cost in money and time seems to be worth it, but what if you could get the major benefits of an image CDN without those costs?
An alternative solution
There are already many open-source solutions for optimizing images on the server side, from build tools to IDE plugins and more. These enable us to easily, or even automatically, produce variants of each image on our web site that are optimized for different conditions.
This blog will cover just one of the many ways to optimize an image. To start, all we need is a way to determine which of our optimized images to serve to the user.
In order to do this, we need to know 2 things:
Size of the screen in pixels
Browser support for WebP or JPEG 2000 formats
51Degrees Device Detection can provide this by identifying the hardware device, operating system, and browser that is being used to make the request. What's more, our Cloud service can supply this information completely free of charge.
.NET Walkthrough
This section is a step-by-step guide to implementing the strategy described above. Our objective will be to modify a simple ASP.NET Core website to serve a WebP image if the format is supported by the browser. For clarity, we'll be performing the image optimization as an automated project task rather than at runtime, but the techniques described here are equally applicable to runtime solutions.
First, make sure that you have the following installed:
Not strictly required, but an IDE is recommended. For this demonstration, we will use Microsoft's freely available IDE Visual Studio Code
First, create the website by opening a terminal, navigating to the directory where the project will live and executing
dotnet new webapp
Let's run the project to make sure it's all working:
Everything looks okay so let's add an image. I'll use the "peering-man" image from the 51Degrees homepage. The original file uses the relatively well-compressed PNG format. However, it is a large image so still weighs in at a hefty 893KB.
We can simply add this image file to our project:
Then add it to the markup on the landing page:
<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
<img src="images/peering-man.png" />
</div>
If we publish this to an Azure app service and run it through Google's PageSpeed tool, we get a score of 72/100. Pretty unimpressive for a website that has virtually no content, apart from this image.
If we look further down, then we can see that our most significant estimated saving comes from simply using a better image format:
We'll be using the gulp-webp package to produce the webp versions of images for us. We need to install this from the terminal with the following commands:
npm install gulp --global
Installs gulp globally so that we can use the "gulp" command from the terminal.
npm install gulp --save-dev
npm install gulp-webp --save-dev
This installs the necessary packages for this project. (The --save-dev part is letting NPM know that these packages are development dependencies, rather than production dependencies.)
Next, create a file called gulpfile.js in the root of your project and copy in the following code:
const gulp = require("gulp");
const webp = require("gulp-webp");
// Define the source and destination locations for images to be converted to webp
function imgToWebP() {
return gulp
.src("./wwwroot/images/*")
.pipe(webp())
.pipe(gulp.dest("./wwwroot/webp"))
}
// Create a gulp task from our function
gulp.task("imgToWebP", imgToWebP);
// Add a watcher to automatically convert any new images to webp
gulp.task("imageWatcher", () => {
gulp.watch("./wwwroot/image/*", imgToWebP)
});
// Define the tasks to be run by default when the 'gulp' command is executed
gulp.task("default", gulp.series("imgToWebP", "imageWatcher"));
Now, run the gulp command:
You should now have a WebP version of the image:
Next, we need to know if the browser supports WebP and modify the page to request the optimized image in this situation.
We could use the <picture> element. However, this is not supported on all browsers so instead, we'll use the 51Degrees Cloud service.
First, we need to create a Resource Key; this tells the Cloud service what we want to know about the device that is connecting to our site.
In this case, we need to know whether the browser supports WebP, so select the WebP property. You can find this on the Cloud Configurator under Web Browser and Apps > Supported Media > WebP, or by using the search bar at the top.
Once you have selected this property, click Next step: Details. You can see confirmation that the property we've selected is a free one. This means that we can use this capability for free up to 500,000 requests per month. This is more than enough for our demo, so tick the box to confirm that the free product is sufficient, then review and accept the Ts&Cs. Now, click the Next step: Implement button.
The next screen contains information to help get up and running with the Resource Key we've just created.
We're going to be using the web integration, which is not covered in the examples on this screen. As such, well just need the Resource Key, so go to that page on the left sidebar, copy the key, and paste it somewhere safe for later.
Next, we need to install some NuGet packages. Visual Studio Code does not support installation of NuGet packages out of the box, so make sure you have an extension such as NuGet Gallery installed, then add the following packages:
FiftyOne.DeviceDetection
FiftyOne.Pipeline.Web
To help get this set up, we'll use the ASP.NET Core integration example from the 51Degrees documentation.
After following this guide, I've made the following additions:
To the Appsettings.json file:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"PipelineOptions": {
"Elements": [
{
"BuilderName": "CloudRequestEngineBuilder",
"BuildParameters": {
"ResourceKey": "--MyResourceKey--"
}
},
{
"BuilderName": "DeviceDetectionCloudEngineBuilder"
}
]
}
}
In Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddSingleton();
services.AddSingleton();
services.AddFiftyOne(Configuration);
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
…
app.UseFiftyOne();
app.UseHttpsRedirection();
app.UseStaticFiles();
…
}
In Index.cshtml.cs:
public class IndexModel : PageModel
{
private readonly ILogger _logger;
private readonly IFlowDataProvider _provider;
public bool SupportsWebP
{
get
{
var flowData = _provider.GetFlowData();
var deviceData = flowData.Get();
return deviceData.WebP.HasValue && deviceData.WebP.Value;
}
}
public IndexModel(ILogger logger,
IFlowDataProvider provider)
{
_logger = logger;
_provider = provider;
}
}
Finally, we can update the view to display either the PNG or WebP versions of the image:
@page
@model IndexModel
@{
ViewData["Title"] = "Home page";
}
<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
@if(Model.SupportsWebP) {
<img src="webp/peering-man.webp" />
} else {
<img src="images/peering-man.png" />
}
</div>
Verify that the page is now serving the WebP image:
Now we can redeploy the app to Azure and see what pagespeed thinks of our changes:
Jackpot!
Summary
This blog explains the reasons for optimizing your images and presents just one approach that can be used to serve next generation image formats to compatible browsers.
Further enhancements could include automating the creation of WebP versions of images at build time and adding support for JPEG 2000 images.
In the real world, other aspects of image optimization are also important. For example, supplying an image that is sized correctly for the screen size and pixel ratio of the device. Getting these details about the device is also possible with the 51Degrees API, and the techniques demonstrated in this blog for optimization of image format could be applied to that challenge as well.