Adding ETag Support to Remix
Before we begin, let's look at a text message exchange between myself and Barack where he asked me what the weather was like. He asked again a short time later and this time I only needed to respond with "Same as last time" because the information hadn't changed between messages. That's basically the idea behind ETag.
Obviously there's a little more to it than that, but the concept is the same: don't repeat information that the asking party already has. Let's dive a little deeper into the details.
What's an ETag and why do you need it?
Let me take a few minutes to explain he mechanics behind ETag and why you might want it support for it on your website. I understand that many Remix developers come from a strict front-end background and full-stack techniques may be new to you. If you are full-stack, then let this act as a refresher (or you can skip ahead to the good stuff below).
A sample session without ETags
When a user visits a website, this is a typical exchange between the web browser and the server.
Step 1
The browser sends a request to the server.
GET / HTTP/1.1
Host: example.com
The first line specifies that we are using the GET
method and requesting the root of the website (i.e. /
). It also indicates the version of the HTTP protocol we are using.
In all of our examples, we are using the GET
method against the path /
on example.com
.
Step 2
The server renders the page and sends the content back to the browser.
HTTP/1.1 200 OK
Content-Type: text/html
<html><body><h1>Hello World</h1></body></html>
The first line of the response contains the HTTP version number and status code. A 200 (OK) indicates that the request was successful.
Any headers that the server wants to send back to the browser are sent after the status code. If there is a body, there is a blank line, followed by the body.
This same exchange is repeated for every request.
A sample session with ETags
Not let's take a look at the same HTTP exchange, but with an added ETag
header.
Step 1
The browser sends a request to the server. This is identical to the previous example.
GET / HTTP/1.1
Host: example.com
Step 2
The server sends the content back to the browser, but this time, an ETag
header is added to the response. The value represents a hash of the response body (or some other identifier). The browser saves the hash value in its cache.
HTTP/1.1 200 OK
Content-Type: text/html
ETag: "c8d9f8c7f8d9f8c7"
<html><body><h1>Hello World</h1></body></html>
HTTP headers keys are case-insensitive. So even though it's commonly written as
ETag
, it could be written asetag
or evenEtAg
.
Step 3
The next time the browser visits the same URL it includes an If-None-Match
header with the cached hash value from the previous visit.
GET / HTTP/1.1
Host: example.com
If-None-Match: "c8d9f8c7f8d9f8c7"
Step 4
The server checks if the hash value matches the hash value in the If-None-Match
header. If the body hasn't changed, then the hashes should match. If this is the case the server sends a 304 (Not Modified) response but does not send a body. This instructs the browser to use the cached content.
HTTP/1.1 304 NOT MODIFIED
Content-Type: text/html
ETag: "c8d9f8c7f8d9f8c7"
As you can see, sending an empty body is much more efficient that sending the entire body — maybe not for our "Hello World" example, but clearly if the body was sized like a typical web page. This means much faster TFP (Time to First Paint) and much less network traffic. The benefits for mobile devices, where bandwidth is often limited, are very apparent.
ETag/If-None-Match does not eliminate the need for the server to re-render subsequent requests, but it does allow the server to skip sending the duplicate rendered content back to the browser, saving bandwidth and time.
Let's get to the good stuff
Now that you see how beneficial this is for your users, let's see how to implement ETags in your Remix application.
As with many aspects of modern JavaScript web development, someone has already done much of the heavy lifting for us. There's a package called remix-etag
that we can drop in with just a few lines of code.
Psst... That someone was me. 😉
First, install the package.
npm i remix-etag
Once installed, edit the file app/entry.server.tsx
and add lines 4 and 22, and change line 18. Here's what your code looks like now.
import { renderToString } from "react-dom/server";
import { RemixServer } from "remix";
import type { EntryContext } from "remix";
import { etag } from 'remix-etag';
export default function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext
) {
let markup = renderToString(
<RemixServer context={remixContext} url={request.url} />
);
responseHeaders.set("Content-Type", "text/html");
const response new Response("<!DOCTYPE html>" + markup, {
status: responseStatusCode,
headers: responseHeaders,
});
return etag({ request, response });
}
That is the entirety of the changes needed to use ETags in your application.
Try it out
First, open a new tab in your browser. Next, open Developer Tools and go to the Network tab. Finally, navigate your app at http://localhost:3000
.
As this is the first request, there will not be an If-None-Match
request header, so the server responds with a status of 200 OK. Inspect the response headers and you'll see that there is an ETag
header. Note that the value will be different from what is shown here.
Now refresh the page. In the request, you should see an If-None-Match
request header with the value of the previous ETag
response header. The response status should be a 304 (Not Modified).
Congratulations! You've implemented an ETag.
Tip of the iceberg
This article just skims the surface of a healthy caching performance strategy remix-etag
is a great starting point. It includes a default Cache-Control
header that should work for most applications. Your mileage may vary, so please consult the docs on the GitHub repo.
You can also add an ETag to your handleDataRequest
function to cache the response of a data request. More on that in a later article.
ETags can reduce the amount of network traffic that is required to render a page. They can be implemented on your site with the remix-etag
package in just a few lines of code.
So what are you waiting for? Add support for ETag to your site now then go ahead and ship it — yes, even on a Partly cloudy, 73º Friday.