Improving Caching With ESI Tags
ESI stands for ‘Edge-Side Includes’, which basically means ‘include a part on my page that is rendered in a different request’. This ‘different request’ will be a request to a CDN or caching proxy server in most cases, such as Akamai or Varnish
For a recent project, we had to create a brand new homepage, fully manageable by our customer using our WebCms functionality. This page would have to be served in three flavors: one for subscribers, one for users that have registered but lack a subscription, and one for anonymous users.
Because this page would be the first thing a user sees when they surf to the website, speed is of the utmost importance. When thinking about speed, the first thing that comes to mind (after optimizing the code, obviously) is to cache a lot of data: when you cache all the data you need on the backend, the load time of the page will improve. But what if we want to improve the load time even more? Well, that’s where the ESI tags enter the picture.
Enter the ESI Tag
ESI stands for ‘Edge-Side Includes’, which basically means ‘include a part on my page that is rendered in a different request’. This ‘different request’ will be a request to a CDN or caching proxy server in most cases, such as Akamai or Varnish.
In general, this solves the problem of having a page that contains both parts that can be cached and parts that can’t be cached (if they contain user-specific data, for example). This can be done by splitting up the HTML in different parts. One part will then be replaced by <esi:include src="http://example.com/1.html"/>
The ESI tag will be processed by the caching proxy server and will launch a request to the source URL. After the request finishes the ESI tag will be replaced by the response. This makes it possible to set different caching durations for different parts of the webpage.
The full specification can be found here.
Implementation
First thing would be to separate all the parts in our webpage that could be cached. We also had to keep in mind that some of the components on the page are rendered at client-side with React.
We decided that everything but the <head> tag could be cached. This way we could expose user-specific data to the React components by placing it in the head, without worrying about wrong data due to a cached page rendering.
When a user requests the homepage, a ‘normal’ request will be launched to the application. Our application will decide which flavor (anonymous, registered, or subscribed) the user has access to and will return the following HTML:
<head>
<!-- some user specific variables -->
<!-- some JavaScript libraries -->
...
</head>
<body>
<esi:include th:src="${'/home/' + pageType}" appendheader="X-ESI: 1" />
</body>
The caching proxy server, in our case Akamai, will see this tag and fetch the HTML returned by the given URL (/home/anonymous for example). The ESI tag will be replaced by this HTML and will be returned to the user. It’s now possible to put the three page flavors in the Akamai cache, which improves the page loading speed tremendously. It’s important to keep in mind that if you return a cached version you should never include user-specific data. In our case this wasn’t a problem, because all user-specific data is fetched client-side by React components.
Because we don’t want a user to be able to access any specific flavor homepage, we built some security in Akamai which will check the ‘X-ESI=1’-header to be active. Without this header the access will be restricted. A clever user could spoof this header by himself, however, so it’s important to keep the application itself secured by protecting it with Oauth2 authentication for example.
During development we didn’t have a proxy server running locally. Because we can simply know if the request is coming from Akamai with the ‘X-ESI’-header, we could write some code to simulate the process of replacing HTML inside a page. If you really need to emulate the ESI replacing locally, it’s possible to run an EdgeSuite Testing Server.
Caveats
Because the ESI specification has never been officially approved by W3C, every implementation could be a little bit different. Be sure to check your caching proxy server’s documentation for the support of ESI tags.
On Akamai, for example, there is a 1 MB size limit of the included webpage, as well as a maximum of five nested levels. For more information, check Akamai's FAQ (.pdf).
Conclusion
ESI tags bring a solution to the problem of being unable to cache a whole page because of user-specific data, by making it possible to split the HTML in different requests.
The concept itself is quite easy, but the implementation can be a lot harder. By using a way to distinguish if a request is launched through ESI, like we did by creating a specific request header, it’s easier in both code and development to check for different cases.
Visitors of the page won’t notice any difference, except of course that the page is blazingly fast.