Month: December 2021

Defensive CSS

Ahmad Shadeed nails it again with “Defensive CSS.” The idea is that you should write CSS to be ready for issues caused by dynamic content.

More items than you thought would be there? No problem, the area can expand or scroll. Title too long? No problem, it either wraps or truncates, and won’t bump into anything weird because margins or gaps are set up. Image come over in an unexpected size? No worries, the layout is designed to make sure the dedicated area is filled with image and will handle the sizing/cropping accordingly.

There is no such thing as being a good CSS developer and not coding defensively. This is what being a CSS developer is, especially when you factor in progressive enhancement concepts and cross-browser/device unknowns.

To Shared LinkPermalink on CSS-Tricks

Defensive CSS originally published on CSS-Tricks


The Web is More Gooder, and Other Observations on Today’s Web Tech

I’m actually working on a talk (whew! been a while! kinda feels good!) about just how good the world of building websites has gotten. I plan to cover a wide swath of web tech, on purpose, because I feel like things have gotten good all around. CSS is doing great, but so is nearly everything else involved in making websites, especially if we take care in what we’re doing.

It also strikes me that updates to the web platform and the ecosystem around it are generally additive. If you feel like the web used to be simpler, well, perhaps it was—but it also still is. Whatever you could do then you can do now, if you want to, although, it would be a fair point if you’re job searching and the expectations to get hired involve a wheelbarrow of complicated tech.

This idea of the web getting better feels like it’s in the water a bit…

Chris Ferdinandi in “Web tech is better. Developer norms are worse.”:

What the modern web can actually do, easily and out-of-the-box, is amazing. My friend Sarah Dayan started her career at around the same time as me, and has a wonderful thread on how things have changed since then.

In particular, Sarah talks about the dramatically improved capabilities of the web and expectations from customers and the people who use it.

Modern web technology is lightyears ahead of the late 2000s.

Wes and Scott on 410 also talk about all kinds of stuff that is great now, from HTML, CSS, and JavaScript to tooling and hosting.

Simeon Griggs in “There’s never been a better time to build websites” has a totally different take on what is great on the web these days than mine, but I appreciate that. The options around building websites have also widened, meaning there are approaches to things that just feel better to people who think and work in different ways.

While there’s absolutely a learning curve to getting started, once you’ve got momentum, modern web development feels like having rocket boosters. The distance between idea and execution is as short as it’s ever been.


, , , ,

New Age DAM APIs to Simplify Your Media Workflows

(This is a sponsored post.)

High-velocity, online businesses produce multiple digital assets like banners, images, videos, PDFs, etc., to promote their businesses online. For such businesses, Digital Asset Management (DAM) solutions are essential. These solutions help centrally store, manage, organize, search and track digital assets. Having a central repository of assets helps in the faster execution of campaigns and improves cross-functional collaboration.

But, for an organization operating at scale and dealing with millions of digital assets flowing in from multiple sources, certain parts of your asset management workflow cannot be done manually using a UI. For example, how do you upload thousands of images in the correct folders every day? Or integrate an internal CMS to add product SKU IDs as a tag on the product image in the DAM?

This is why leading DAM solutions come with APIs to allow you to integrate them into your existing workflows and get the benefits of a DAM system at scale. Let’s first understand what an API is before getting to some common examples and use cases you can solve with them.

What is an API?

API stands for Application Programming Interface. It allows two software pieces or applications to communicate using a common definition.

An analogy in the physical world is when you order a dish in a restaurant, the chef understands what you ordered and prepares it. Here, the menu with the dish’s name serves as the common language for you (one of the parties) to communicate with the chef (the other party).

Let’s look at an example of an API in an e-commerce application. To check the delivery time to your location, you enter your pin code, and in a second or two, the time appears on your mobile screen. Here, your app (one of the software) is talking to the server (the other software), asking to give delivery times for a pin code (the definition or the common language between the two software). The delivery time that is returned by the server is called an API’s “response.”

What is a DAM API?

Continuing our explanation above, DAM APIs allow you to communicate with the Digital Asset Management system using a defined language. These APIs allow you to use all or most of the features of a DAM system, but instead of doing it via the user interface in a browser, you would be able to use them from a software program.

For example, a DAM’s user interface lets you drag and drop an image to upload it. However, the same DAM system could offer an API to upload images from your user’s Android app. Here the Android app is one of the software, the DAM system itself is the other software, and the upload API communicates what and how to upload to the DAM system. Once completed, the API responds with information about the uploaded image.

What’s ImageKit? What’s its DAM offering?

ImageKit is a leading Digital Asset Management solution. It comes with standard DAM features like storage, management, AI tagging, custom metadata, and advanced search. It also has optimized asset delivery integrated into the system.

While ImageKit’s DAM system comes with a user-friendly UI, like all leading players in this space, it also offers media APIs to use all of its features programmatically.

Use cases you can solve with DAM APIs

Before jumping to the APIs, here are some ways to use a DAM system’s APIs.

  1. If you have an app or website where users can upload images or videos, or other content,you can use the DAM API to upload them directly to the DAM system.
  2. Suppose you build a product that offers integrated media storage to its users. Instead of exposing your users to the DAM system directly, you would want to integrate it into your product natively (or white-label it). You can use a combination of DAM upload APIs, list and search APIs, and get image detail APIs to build this asset library for the users of your product.
  3. Suppose your team uses an existing CMS or any other system to manage internal data. You can use the DAM as the underlying file storage and use its advanced management and search features via its APIs. Your team never has to leave their existing CMS while still leveraging all the features of the DAM system.
  4. If you require and your DAM solution supports it, you can use the real-time image and video optimization APIs to deliver the assets to your users or on different platforms. ImageKit is one such DAM that supports file delivery for any asset upload to its media library.

Common Digital Asset Management APIs

Let’s look at some of the standard APIs that most DAM systems offer. For demonstration and examples, we would be using ImageKit’s DAM APIs.

1. API for uploading a file

This is the most basic API of all — before you use the DAM system, you need to upload files to it.

ImageKit’s Upload API allows you to upload an actual file from your file system or a web URL. You can use this API on a front-end application, like a mobile app, or a back-end application, like your application server. Here is an example of uploading the image from a back-end application.

curl -X POST ""   -u your_private_api_key:   -F 'file=@/Users/username/Desktop/my_file_name.jpg;type=image/jpg'   -F 'fileName=my_file_name.jpg'

You would get some information about the uploaded file in the API response. For example, you would usually get a unique ID for your file, which would be super valuable for subsequent APIs, along with other information like the file’s format, size, upload time, etc.

{   "fileId": "598821f949c0a938d57563bd",   "name": "my_file_name.jpg",   "url": "",   "height": 300,   "width": 200,   "size": 83622,   // other information... }

2. Moving, copying, and deleting a file

After uploading a file to the DAM system, you might want to remove it or move it around to different folders. This also can be done programmatically via APIs.

For example, in ImageKit, to move a file from one folder to the other, you need to give the file’s path (sourceFilePath) and the destination folder path (destinationPath) in the API.

curl -X POST ""  -H 'Content-Type: application/json'      -u your_private_key: -d '     {   "sourceFilePath" : "/path/to/file.jpg",   "destinationPath" : "/folder/to/move/into/" }'

3. Updating a file with manual and AI Tags

File nomenclature and creating the correct folder structure are often insufficient to organize and find content in a growing repository of digital assets.

Associating custom metadata or tags with an asset helps build another layer of organization for your content. For example, you could assign values to fields such as “Product Category” (Shoe, Shirt, Jeans, etc.), “Platform” (Facebook, Instagram, etc.), “Sale Name” (Thanksgiving, Black Friday, etc.) to the files in your DAM system, to build a more business-specific organization.

Through services like Google Cloud Vision, taking advantage of AI can help speed up asset tagging workflows and reduce errors. In addition, good DAM systems do provide APIs to associate tags with your assets.

For example, ImageKit allows you to add AI-inferred tags, using Google Cloud Vision, to your asset in the code below.

curl -X PATCH ""     -H 'Content-Type: application/json'    -u your_private_key: -d'    {     "extensions": [     {       "name": "google-auto-tagging",       "maxTags": 5,       "minConfidence": 95     }   ] }

While the above API adds tags to an existing file, you can also do this when the file is first uploaded.

4. Searching for a file using search APIs

The most significant advantage of using a DAM is searching for the exact asset amongst thousands of them. Therefore, a good search API is necessary for any DAM system. It should allow searching on all the possible parameters associated with an asset, including custom tags and metadata that we add to create a business-specific organization for ourselves.

ImageKit provides a very flexible search API that lets you construct complex search queries to pinpoint the exact resource you need. The example below finds out all assets you created more than seven days ago with a size of more than 2MB.

curl -X GET ""   -G --data-urlencode "searchQuery=createdAt >= "7d" AND size > "2mb""   -u your_private_api_key:

5. The image and video delivery API

Once your team starts managing and collaborating on the assets on the DAM, the next obvious step would be to be able to use these assets on the web, share them via their URLs, use them on your website, apps, emails, and so on.

Leading DAM solutions like ImageKit provide ready-to-use URLs for any file stored with them. ImageKit API also has in-built automatic optimizations and real-time manipulations for images and videos that ensure optimized asset delivery every time.,h-200

The above example resizes the original image to a 200×200 square thumbnail while compressing it and optimizing its format. But, of course, you can do the same using a similar URL-based API for videos too.Read more about ImageKit’s media APIs


Apart from the basic APIs explained above, all DAM solutions offer several other APIs that allow you to manage folders, get file details, control the shareability of assets, and more. The possibilities are endless for integrating these APIs to simplify and automate your existing workflows. Using a DAM solution like ImageKit, with its extensive media management APIs given here, will bring your marketing, creative, and technology teams on the same page and help them execute campaigns faster. Sign up today on ImageKit’s forever free DAM plan and start optimizing your media workflows.


, , ,

Add Less

When you’re about to start a new website, what do you think first? Do you start with a library or framework you know, like React or Vue, or a meta-framework on top of that, like Next or Nuxt? Do you pull up a speedy build tool like Vite, or configure your webpack?

There’s a great tweet by Phil Hawksworth that I bookmarked a few years back and still love to this day:

Your websites start fast until you add too much to make them slow. Do you need any framework at all? Could you do what you want natively in the browser? Would doing it without a framework at all make your site lighter, or actually heavier in the long run as you create or optimize what others have already done?

I personally love the idea of shipping less code to ultimately ship more value to the browser. Understanding browser APIs and what comes “for free” could actually lead to less reinventing the wheel, and potentially more accessibility as you use the tools provided.

Instead of pulling in a library for every single task you want to do, try to look under the hood at what they are doing. For example, in a project I was maintaining, I noticed that we had a React component imported that was shipping an entire npm package for a small (less than 10-line) component with some CSS sprinkled on top (that we were overriding with our own design system). When we re-wrote that component from scratch, our bundle size was smaller, we were able to customize it more, and we didn’t have to work around someone else’s decisions.

Now, I’m not saying you shouldn’t use any libraries or frameworks or components out there. Open source exists for a reason! What I am saying is to be discerning about what you bring into your projects. Let the power of the browser work for you, and use less stuff!



Game design vs level design


How to Make and Sell Your Own Merch


On Yak Shaving and md-block, a new HTML element for Markdown

Lea Verou made a Web Component for processing Markdown. Looks like there were a couple of others out there already, but I agree with Lea in that this is a good use case for the light DOM (as opposed to the shadow DOM that is normally quite useful for web components), and that’s what Lea’s does. The output is HTML so I can imagine it’s ideal you can style it on the page like any other type rather than have to deal with that shadow DOM. I still feel like the styling stories for shadow DOM all kinda suck.

The story of how it came to be is funny and highly relatable. You just want to build one simple thing and it turns out you have to do 15 other things and it takes the better part of a week.

The demos on the landing page for <md-block> shoot over to CodePen using the prefill API. Figured I’d embed one here too:

To Shared LinkPermalink on CSS-Tricks


, , , ,

Empathetic Animation

Animation on the web is often a contentious topic. I think, in part, it’s because bad animation is blindingly obvious, whereas well-executed animation fades seamlessly into the background. When handled well, animation can really elevate a website, whether it’s just adding a bit of personality or providing visual hints and lessening cognitive load. Unfortunately, it often feels like there are two camps, accessibility vs. animation. This is such a shame because we can have it all! All it requires is a little consideration.

Here’s a couple of important questions to ask when you’re creating animations.

Does this animation serve a purpose?

This sounds serious, but don’t worry — the site’s purpose is key. If you’re building a personal portfolio, go wild! However, if someone’s trying to file a tax return, whimsical loading animations aren’t likely to be well-received. On the other hand, an animated progress bar could be a nice touch while providing visual feedback on the user’s action.

Is it diverting focus from important information?

It’s all too easy to get caught up in the excitement of whizzing things around, but remember that the web is primarily an information system. When people are trying to read, animating text or looping animations that play nearby can be hugely distracting, especially for people with ADD or ADHD. Great animation aids focus; it doesn’t disrupt it.

So! Your animation’s passed the test, what next? Here are a few thoughts…

Did we allow users to opt-out?

It’s important that our animations are safe for people with motion sensitivities. Those with vestibular (inner ear) disorders can experience dizziness, headaches, or even nausea from animated content.

Luckily, we can tap into operating system settings with the prefers-reduced-motion media query. This media query detects whether the user has requested the operating system to minimize the amount of animation or motion it uses.

Screenshot of the user preferences settings in MacOS, open to Accessibility and displaying options for how to display things, including one option for reduce motion, which is checked.
The reduced motion settings in macOS.

Here’s an example:

@media (prefers-reduced-motion: reduce) {   *,   *::before,   *::after {     animation-duration: 0.01ms !important;     animation-iteration-count: 1 !important;     transition-duration: 0.01ms !important;     scroll-behavior: auto !important;   } }

This snippet taps into that user setting and, if enabled, it gets rid of all your CSS animations and transitions. It’s a bit of a sledgehammer approach though — remember, the key word in this media query is reduced. Make sure functionality isn’t breaking and that users aren’t losing important context by opting out of the animation. I prefer tailoring reduced motion options for those users. Think simple opacity fades instead of zooming or panning effects.

What about JavaScript, though?

Glad you asked! We can make use of the reduced motion media query in JavaScript land, too!

let motionQuery = matchMedia('(prefers-reduced-motion)');  const handleReduceMotion = () => {   if (motionQuery.matches) {     // reduced motion options   } }  motionQuery.addListener(handleReduceMotion); handleReduceMotion()

Tapping into system preferences isn’t bulletproof. After all, it’s there’s no guarantee that everyone affected by motion knows how to change their settings. To be extra safe, it’s possible to add a reduced motion toggle in the UI and put the power back in the user’s hands to decide. We {the collective} has a really nice implementation on their site

Here’s a straightforward example:

Scroll animations

One of my favorite things about animating on the web is hooking into user interactions. It opens up a world of creative possibilities and really allows you to engage with visitors. But it’s important to remember that not all interactions are opt-in — some (like scrolling) are inherently tied to how someone navigates around your site.

The Nielson Norman Group has done some great research on scroll interactions. One particular part really stuck out for me. They found that a lot of task-focused users couldn’t tell the difference between slow load times and scroll-triggered entrance animations. All they noticed was a frustrating delay in the interface’s response time. I can relate to this; it’s annoying when you’re trying to scan a website for some information and you have to wait for the page to slowly ease and fade into view.

If you’re using GreenSock’s ScrollTrigger plugin for your animations, you’re in luck. We’ve added a cool little property to help avoid this frustration: fastScrollEnd.

fastScrollEnd detects the users’ scroll velocity. ScrollTrigger skips the entrance animations to their end state when the user scrolls super fast, like they’re in a hurry. Check it out!

There’s also a super easy way to make your scroll animations reduced-motion-friendly with ScrollTrigger.matchMedia():

I hope these snippets and insights help. Remember, consider the purpose, lead with empathy, and use your animation powers responsibly!



Our favorite Chrome extensions of 2021

I hadn’t heard of most of the Chrome extensions that Sarem Gizaw lists as 2021 favorites. Here are my hot takes on all of them, except the virtual learning specific ones that aren’t very relevant to me.

Browser extensions have come a long way toward being cross-browser compatible, so I’d think a lot of these are available for Safari and Firefox now—or at least could be without enormous amounts of work if the authors felt like doing it.

Notably, there are no ad blocker plugins in the list. Not a huge surprise there, even though I’m sure they are some of the most-downloaded and used. I use Ghostery, but I haven’t re-evaluated the landscape there in a while. I like how Ghostery makes it easy for me to toggle on-and-off individual scripts, both on individual sites and broadly across all sites. That means I could enable BuySellAds (something even Adblock Plus does by default) and Google Analytics scripts, but turn off A/B testers or gross ad networks.

To Shared LinkPermalink on CSS-Tricks


, , ,

Add a Service Worker to Your Site

One of the best things you can do for your website in 2022 is add a service worker, if you don’t have one in place already. Service workers give your website super powers. Today, I want to show you some of the amazing things that they can do, and give you a paint-by-numbers boilerplate that you can use to start using them on your site right away.

What are service workers?

A service worker is a special type of JavaScript file that acts like middleware for your site. Any request that comes from the site, and any response it gets back, first goes through the service worker file. Service workers also have access to a special cache where they can save responses and assets locally.

Together, these features allow you to…

  • Serve frequently accessed assets from your local cache instead of the network, reducing data usage and improving performance.
  • Provide access to critical information (or even your entire site or app) when the visitor goes offline.
  • Prefetch important assets and API responses so they’re ready when the user needs them.
  • Provide fallback assets in response to HTTP errors.

In short, service workers allow you to build faster and more resilient web experiences.

Unlike regular JavaScript files, service workers do not have access to the DOM. They also run on their own thread, and as a result, don’t block other JavaScript from running. Service workers are designed to be fully asynchronous.


Because service workers intercept every request and response for your site or app, they have some important security limitations.

Service workers follow a same-origin policy.

You can’t run your service worker from a CDN or third party. It has to be hosted at the same domain as where it will be run.

Service workers only work on sites with an installed SSL certificate.

Many web hosts provide SSL certificates at no cost or for a small fee. If you’re comfortable with the command line, you can also install one for free using Let’s Encrypt.

There is an exception to the SSL certificate requirement for localhost testing, but you can’t run your service worker from the file:// protocol. You need to have a local server running.

Adding a service worker to your site or web app

To use a service worker, the first thing we need to do is register it with the browser. You can register a service worker using the navigator.serviceWorker.register() method. Pass in the path to the service worker file as an argument.


You can run this in an external JavaScript file, but prefer to run it directly in a script element inline in my HTML so that it runs as soon as possible.

Unlike other types of JavaScript files, service workers only work for the directory in which they exist (and any of its sub-directories). A service worker file located at /js/sw.js would only work for files in the /js directory. As a result, you should place your service worker file inside the root directory of your site.

While service workers have fantastic browser support, it’s a good idea to make sure the browser supports them before running your registration script.

if (navigator && navigator.serviceWorker) {   navigator.serviceWorker.register('sw.js'); }

After the service worker installs, the browser can activate it. Typically, this only happens when…

  • there is no service worker currently active, or
  • the user refreshes the page.

The service worker won’t run or intercept requests until it’s activated.

Listening for requests in a service worker

Once the service worker is active, it can start intercepting requests and running other tasks. We can listen for requests with self.addEventListener() and the fetch event.

// Listen for request events self.addEventListener('fetch', function (event) {   // Do stuff... });

Inside the event listener, the event.request property is the request object itself. For ease, we can save it to the request variable.

Certain versions of the Chromium browser have a bug that throws an error if the page is opened in a new tab. Fortunately, there’s a simple fix from Paul Irish that I include in all of my service workers, just in case:

// Listen for request events self.addEventListener('fetch', function (event) {    // Get the request   let request = event.request;    // Bug fix   //   if (event.request.cache === 'only-if-cached' && event.request.mode !== 'same-origin') return;  }); 

Once your service worker is active, every single request is sent through it, and will be intercepted with the fetch event.

Service worker strategies

Once your service worker is installed and activated, you can intercept requests and responses, and handle them in various ways. There are two primary strategies you can use in your service worker:

  1. Network-first. With a network-first approach, you pass along requests to the network. If the request isn’t found, or there’s no network connectivity, you then look for the request in the service worker cache.
  2. Offline-first. With an offline-first approach, you check for a requested asset in the service worker cache first. If it’s not found, you send the request to the network.

Network-first and offline-first approaches work in tandem. You will likely mix-and-match approaches depending on the type of asset being requested.

Offline-first is great for large assets that don’t change very often: CSS, JavaScript, images, and fonts. Network-first is a better fit for frequently updated assets like HTML and API requests.

Strategies for caching assets

How do you get assets into your browser’s cache? You’ll typically use two different approaches, depending on the types of assets.

  1. Pre-cache on install. Every site and web app has a set of core assets that are used on almost every page: CSS, JavaScript, a logo, favicon, and fonts. You can pre-cache these during the install event, and serve them using an offline-first approach whenever they’re requested.
  2. Cache as you browser. Your site or app likely has assets that won’t be accessed on every visit or by every visitor; things like blog posts and images that go with articles. For these assets, you may want to cache them in real-time as the visitor accesses them.

You can then serve those cached assets, either by default or as a fallback, depending on your approach.

Implementing network-first and offline-first strategies in your service worker

Inside a fetch event in your service worker, the request.headers.get('Accept') method returns the MIME type for the content. We can use that to determine what type of file the request is for. MDN has a list of common files and their MIME types. For example, HTML files have a MIME type of text/html.

We can pass the type of file we’re looking for into the String.includes() method as an argument, and use if statements to respond in different ways based on the file type.

// Listen for request events self.addEventListener('fetch', function (event) {    // Get the request   let request = event.request;    // Bug fix   //   if (event.request.cache === 'only-if-cached' && event.request.mode !== 'same-origin') return;    // HTML files   // Network-first   if (request.headers.get('Accept').includes('text/html')) {     // Handle HTML files...     return;   }    // CSS & JavaScript   // Offline-first   if (request.headers.get('Accept').includes('text/css') || request.headers.get('Accept').includes('text/javascript')) {     // Handle CSS and JavaScript files...     return;   }    // Images   // Offline-first   if (request.headers.get('Accept').includes('image')) {     // Handle images...   }  });


Inside each if statement, we use the event.respondWith() method to modify the response that’s sent back to the browser.

For assets that use a network-first approach, we use the fetch() method, passing in the request, to pass through the request for the HTML file. If it returns successfully, we’ll return the response in our callback function. This is the same behavior as not having a service worker at all.

If there’s an error, we can use Promise.catch() to modify the response instead of showing the default browser error message. We can use the caches.match() method to look for that page, and return it instead of the network response.

// Send the request to the network first // If it's not found, look in the cache event.respondWith(   fetch(request).then(function (response) {     return response;   }).catch(function (error) {     return caches.match(request).then(function (response) {       return response;     });   }) );


For assets that use an offline-first approach, we’ll first check inside the browser cache using the caches.match() method. If a match is found, we’ll return it. Otherwise, we’ll use the fetch() method to pass the request along to the network.

// Check the cache first // If it's not found, send the request to the network event.respondWith(   caches.match(request).then(function (response) {     return response || fetch(request).then(function (response) {       return response;     });   }) );

Pre-caching core assets

Inside an install event listener in the service worker, we can use the method to open a service worker cache. We pass in the name we want to use for the cache, app, as an argument.

The cache is scoped and restricted to your domain. Other sites can’t access it, and if they have a cache with the same name the contents are kept entirely separate.

The method returns a Promise. If a cache already exists with this name, the Promise will resolve with it. If not, it will create the cache first, then resolve.

// Listen for the install event self.addEventListener('install', function (event) {   event.waitUntil('app')); });

Next, we can chain a then() method to our method with a callback function.

In order to add files to the cache, we need to request them, which we can do with the new Request() constructor. We can use the cache.add() method to add the file to the service worker cache. Then, we return the cache object.

We want the install event to wait until we’ve cached our file before completing, so let’s wrap our code in the event.waitUntil() method:

// Listen for the install event self.addEventListener('install', function (event) {    // Cache the offline.html page   event.waitUntil('app').then(function (cache) {     cache.add(new Request('offline.html'));     return cache;   }));  });

I find it helpful to create an array with the paths to all of my core files. Then, inside the install event listener, after I open my cache, I can loop through each item and add it.

let coreAssets = [   '/css/main.css',   '/js/main.js',   '/img/logo.svg',   '/img/favicon.ico' ];  // On install, cache some stuff self.addEventListener('install', function (event) {    // Cache core assets   event.waitUntil('app').then(function (cache) {     for (let asset of coreAssets) {       cache.add(new Request(asset));     }     return cache;   }));  });

Cache as you browse

Your site or app likely has assets that won’t be accessed on every visit or by every visitor; things like blog posts and images that go with articles. For these assets, you may want to cache them in real-time as the visitor accesses them. On subsequent visits, you can load them directly from cache (with an offline-first approach) or serve them as a fallback if the network fails (using a network-first approach).

When a fetch() method returns a successful response, we can use the Response.clone() method to create a copy of it.

Next, we can use the method to open our cache. Then, we’ll use the cache.put() method to save the copied response to the cache, passing in the request and copy of the response as arguments. Because this is an asynchronous function, we’ll wrap our code in the event.waitUntil() method. This prevents the event from ending before we’ve saved our copy to cache. Once the copy is saved, we can return the response as normal.

/explanation We use cache.put() instead of cache.add() because we already have a response. Using cache.add() would make another network call.

// HTML files // Network-first if (request.headers.get('Accept').includes('text/html')) {   event.respondWith(     fetch(request).then(function (response) {        // Create a copy of the response and save it to the cache       let copy = response.clone();       event.waitUntil('app').then(function (cache) {         return cache.put(request, copy);       }));        // Return the response       return response;    }).catch(function (error) {       return caches.match(request).then(function (response) {         return response;       });     })   ); }

Putting it all together

I’ve put together a copy-paste boilerplate for you on GitHub. Add your core assets to the coreAssets array, and register it on your site to get started.

If you do nothing else, this will be a huge boost to your site in 2022.

But there’s so much more you can do with service workers. There are advanced caching strategies for APIs. You can provide an offline page with critical information if a visitor loses their network connection. You can clean up bloated caches as the user browses.

Jeremy Keith’s book, Going Offline, is a great primer on service workers. If you want to take things to the next level and dig into progressive web apps, Jason Grigsby’s book dives into the various strategies you can use.

And for a pragmatic deep dive you can complete in about an hour, I also have a course and ebook on service workers with lots of code examples and a project you can work on.


, ,