Chapter 17. Static Content

Static content refers to the resources your app will be serving that don’t change on a per-request basis. Here are the usual suspects:

Multimedia

Images, videos, and audio files. It’s quite possible to generate image files on the fly, of course (and video and audio, though that’s far less common), but most multimedia resources are static.

HTML

If our web application is using views to render dynamic HTML, it wouldn’t generally qualify as static HTML (though for performance reasons, you may dynamically generate HTML, cache it, and serve it as a static resource). SPA applications, as we’ve seen, commonly send a single, static HTML file to the client, which is the most common reason to treat HTML as a static resource. Note that requiring the client to use an .html extension is not very modern, so most servers now allow static HTML resources to be served without the extension (so /foo and /foo.html would return the same content).

CSS

Even if you use an abstracted CSS language like LESS, Sass, or Stylus, at the end of the day, your browser needs plain CSS, which is a static resource.1

JavaScript

Just because the server is running JavaScript doesn’t mean there won’t be client-side JavaScript. Client-side JavaScript is considered a static resource. Of course, now the line is starting to get a bit hazy: what if there was common code that we wanted to use on the backend and client side? There are ways to solve this problem, but at the end of the day, the JavaScript that gets sent to the client is generally static.

Binary downloads

This is the catchall category: any PDFs, ZIP files, Word documents, installers, and the like.

Note

If you are building an API only, there may be no static resources. If that’s the case, you may skip this chapter.

Performance Considerations

The way you handle static resources significantly impacts the real-world performance of your website, especially if your site is multimedia-heavy. The two primary performance considerations are reducing the number of requests and reducing content size.

Of the two, reducing the number of (HTTP) requests is more critical, especially for mobile (the overhead of making an HTTP request is significantly higher over a cellular network). Reducing the number of requests can be accomplished in two ways: combining resources and browser caching.

Combining resources is primarily an architectural and frontend concern: as much as possible, small images should be combined into a single sprite. Then use CSS to set the offset and size to display only the portion of the image you want. For creating sprites, I highly recommend the free service SpritePad. It makes generating sprites incredibly easy, and it generates the CSS for you as well. Nothing could be easier. SpritePad’s free functionality is probably all you’ll ever need, but if you find yourself creating a lot of sprites, you might find their premium offerings worth it.

Browser caching helps reduce HTTP requests by storing commonly used static resources in the client’s browser. Though browsers go to great lengths to make caching as automatic as possible, it’s not magic: there’s a lot you can and should do to enable browser caching of your static resources.

Lastly, we can increase performance by reducing the size of static resources. Some techniques are lossless (size reduction can be achieved without losing any data), and some techniques are lossy (size reduction is achieved by reducing the quality of static resources). Lossless techniques include minification of JavaScript and CSS, and optimizing PNG images. Lossy techniques include increasing JPEG and video compression levels. We’ll be discussing minification and bundling (which also reduces HTTP requests) in this chapter.

Tip

The importance of reducing HTTP requests will diminish over time as HTTP/2 becomes more commonplace. One of the primary improvements in HTTP/2 is request and response multiplexing, which reduces the overhead of fetching multiple resources in parallel. See “Introduction to HTTP/2” by Ilya Grigorikfor more information.

Content Delivery Networks

When you move your website into production, the static resources must be hosted on the internet somewhere. You may be used to hosting them on the same server where all your dynamic HTML is generated. Our example so far has also taken this approach: the Node/Express server we spin up when we type node meadowlark.js serves all of the HTML as well as static resources. However, if you want to maximize the performance of your site (or allow for doing so in the future), you will want to make it easy to host your static resources on a content delivery network (CDN). A CDN is a server that’s optimized for delivering static resources. It leverages special headers (that we’ll learn about soon) that enable browser caching.

CDNs also can enable geographic optimization (often called edge caching); that is, they can deliver your static content from a server that is geographically closer to your client. While the internet is very fast indeed (not operating at the speed of light, exactly, but close enough), it is still faster to deliver data over a hundred miles than a thousand. Individual time savings may be small, but if you multiply across all of your users, requests, and resources, it adds up fast.

Most of your static resources will be referenced in HTML views (<link> elements to CSS files, <script> references to JavaScript files, <img> tags referencing images, and multimedia embedding tags). It is also common to have static references in CSS, usually the background-image property. Lastly, static resources are sometimes referenced in JavaScript, such as JavaScript code that dynamically changes or inserts <img> tags or the background-image property.

Note

You generally don’t have to worry about cross-domain resource sharing (CORS) when using a CDN. External resources loaded in HTML aren’t subject to CORS policy: you have to enable CORS only for resources that are loaded via Ajax (see Chapter 15).

Designing for CDNs

The architecture of your site will influence how you use a CDN. Most CDNs let you configure routing rules to determine where to send incoming requests. While you can get arbitrarily sophisticated with those routing rules, it usually boils down to sending requests for static assets to one location (usually provided by your CDN) and requests for dynamic endpoints (like dynamic pages or API endpoints) to another.

Choosing and configuring a CDN is a big topic, which I won’t get into here, but I will arm you with background knowledge that will help you configure your CDN of choice.

The easiest approach to structuring your application is to make it easy to distinguish dynamic from static assets to make the CDN routing rules as simple as possible. While it’s possible to do this using subdomains (dynamic assets are served from meadowlark.com, and static assets are served from static.meadowlark.com, for example), this approach has extra complications and makes local development more difficult. The easier approach is to use the request paths: everything that starts with /public/ is a static asset, and everything else is dynamic, for example. The approach may be different if you’re generating your content with Express or using Express to provide an API for a single-page application.

Server-Rendered Website

If you’re using Express to render your dynamic HTML, it’s easier to say, “Everything that starts with /static/ is a static asset, and everything else is dynamic.” With this approach, all of your (dynamically generated) URLs would be whatever you want them to be (as long as they don’t start with /static/, of course!), and all of your static assets will be prefixed with /static/:

  <img src="/static/img/meadowlark-logo-1.png" alt="Meadowlark Logo">
  Welcome to <a href="/about">Meadowlark Travel</a>.

So far in this book, we’ve been using the Express static middleware as if it were hosting all of the static assets at the root. That is, if we put a static asset foo.png in the public directory, we reference it with the URL path /foo.png, not /static/foo.png. We could, of course, create a subdirectory static inside our existing public directory, so /public/static/foo.png would have the URL /static/foo.png but that seems a little silly. Fortunately, the static middleware saves us from that silliness. All we have to do is specify a different path when we call app.use:

app.use('/static', express.static('public'))

Now we can use the same URL structure in our development environment that we will in production. If we’re careful about keeping our public directory in sync with what’s in our CDN, we can reference the same static assets in both places, and move seamlessly between development and production.

When we configure routing for our CDN (you’ll have to consult your CDN’s documentation for this), your routing will look like this:

URL path Routing destination / origin

/static/*

Static CDN file store

/* (everything else)

Your Node/Express server, proxy, or load balancer

Single-Page Applications

Single-page applications will typically be the opposite of a server-rendered website: only the API will be routed to your server (for example, any request prefixed with /api), and everything else will be rerouted to your static file store.

As we saw in Chapter 16, you will have some way to create a production bundle for your application, which will include all of the static resources, which you’ll upload to your CDN. Then all you have to do is make sure routing to your API is configured correctly. So your routing will look like this:

URL path Routing destination / origin

/api/*

Your Node/Express server, proxy, or load balancer

/* (everything else)

Static CDN file store

Now that we’ve seen how we might structure an application so we can seamlessly move from development to production, let’s turn our attention to what’s actually happening with caching and how it improves performance.

Caching Static Assets

Whether you’re using Express to serve static assets or using a CDN, it’s helpful to understand the HTTP response headers your browser uses to determine when and how to cache static assets:

Expires/Cache-Control

These two headers tell your browser the maximum amount of time a resource can be cached. They are taken seriously by the browser: if they inform the browser to cache something for a month, it simply won’t re-download it for a month, as long as it stays in the cache. It’s important to understand that a browser may remove the image from the cache prematurely, for reasons you have no control over. For example, the user could clear the cache manually, or the browser could clear your resource to make room for other resources the user is visiting more frequently. You need one only of these headers, and Expires is more broadly supported, so it’s preferable to use that one. If the resource is in the cache, and it has not expired yet, the browser will not issue a GET request at all, which improves performance, especially on mobile.

Last-Modified/ETag

These two tags provide a versioning of sorts: if the browser needs to fetch the resource, it will examine these tags before downloading the content. A GET request is still issued to the server, but if the values returned by these headers satisfy the browser that the resource hasn’t changed, it will not proceed to download the file. As the name indicates, Last-Modified allows you to specify the date the resource was last modified. ETag allows you to use an arbitrary string, which is usually a version string or a content hash.

When serving static resources, you should use the Expires header and either Last-Modified or ETag. The Express built-in static middleware sets Cache-Control, but doesn’t handle either Last-Modified or ETag. So, while it’s suitable for development, it’s not a great solution for deployment.

If you choose to host your static resources on a CDN, such as Amazon CloudFront, Microsoft Azure, Fastly, Cloudflare, Akamai, or StackPath, the advantage is that they will handle most of these details for you. You will be able to fine-tune the details, but the defaults provided by any of these services are usually good out of the box.

Changing Your Static Content

Caching significantly improves the performance of your website, but it isn’t without its consequences. In particular, if you change any of your static resources, clients may not see them until the cached versions expire in your browser. Google recommends you cache for a month, preferably a year. Imagine a user who uses your website every day on the same browser: that person might not see your updates for a whole year!

Clearly this is an undesirable situation, and you can’t just tell your users to clear their cache. The solution is cache busting. Cache busting is a technique for giving you control of when your user’s browser is forced to re-download an asset. Usually this amounts to versioning the asset (main.2.css or main.css?version=2) or adding some kind of hash (main.e16b7e149dccfcc399e025e0c454bf77.css). Whatever technique you use, when you update the asset, the resource name changes, and the browser knows it needs to download it.

We can do the same thing with our multimedia assets. Let’s take our logo, for example (/static/img/meadowlark_logo.png). If we host it on a CDN for maximum performance, specifying an expiration of one year, and then change the logo, your users may not see the updated logo for up to a year. However, if you rename your logo /static/img/meadowlark_logo-1.png (and reflect that name change in your HTML), the browser will be forced to download it, because it looks like a new resource.

If you’re using a single-page application framework, such as create-react-app or similar, they will provide a build step that will create production-ready resource bundles that have hashes appended.

If you’re starting from scratch, you’ll probably want to look into a bundler (which is what the SPA frameworks use under the hood). Bundlers combine your JavaScript, CSS, and some other types of static assets into as few as possible, and minify the result (making it as small as possible). Bundler configuration is a big topic, but fortunately there is a lot of good documentation out there. The most popular bundlers available right now are as follows:

Webpack

Webpack was one of the first bundlers to really take off, and it still maintains a huge following. It’s very sophisticated, but that sophistication comes at a cost: the learning curve is steep. However, it’s good to at least know the basics.

Parcel

Parcel is the newcomer, and it has made a big splash. It’s extremely well-documented, extremely fast, and, best of all, has the shortest learning curve. If you’re looking to get the job done quickly, without a lot of fuss, start here.

Rollup

Rollup sits somewhere between Webpack and Parcel. Like Webpack, it’s very robust and has a lot of features. However, it is easier to get started with than Webpack, and not as simple as Parcel.

Conclusion

For what seems like such a simple thing, static resources can be a lot of trouble. However, they probably represent the bulk of the data actually being transferred to your visitors, so spending some time optimizing them will yield substantial payoff.

A viable solution to static assets not previously mentioned is to simply host your static resources on a CDN from the start, and always use the full URL to the resource in your views and CSS. This has the advantage of simplicity, but if you ever want to spend a weekend hackathon at that cabin in the woods without internet access, you’d be in trouble!

Elaborate bundling and minification is another area in which you can save time if the payoff isn’t worth it for your application. In particular, if your site includes only one or two JavaScript files, and all of your CSS lives in a single file, you could probably skip bundling altogether, but real-world applications have a tendency to grow over time.

Whatever technique you choose to use to serve your static resources, I highly recommend hosting them separately, preferably on a CDN. If it sounds like a hassle to you, let me assure that it’s not nearly as difficult as it sounds, especially if you spend a little time on your deployment system, so deploying static resources to one location and your application to another is automatic.

If you’re concerned about the hosting costs of CDNs, I encourage you to take a look at what you’re paying now for hosting. Most hosting providers essentially charge for bandwidth, even if you don’t know it. However, if all of a sudden your site is mentioned on Slashdot, and you get “Slashdotted,” you may find yourself with a hosting bill you didn’t expect. CDN hosting is usually set up so that you pay for what you use. To give you an example, a website that I once managed for a medium-sized regional company, which used about 20 GB a month of bandwidth, paid only a few dollars per month to host static resources (and it was a very media-heavy site).

The performance gains you realize by hosting your static resources on a CDN are significant, and the cost and inconvenience of doing so is minimal, so I highly recommend going this route.

1 It is possible to use uncompiled LESS in a browser, with some JavaScript magic. There are performance consequences to this approach, so I don’t recommend it.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.12.154.121