Claude
Skills
Sign in
Back

import-on-interaction

Included with Lifetime
$97 forever

Teaches interaction-based lazy loading for non-critical resources. Use when you have heavy components or libraries that are only needed after user interaction like clicks, hovers, or form input.

General

What this skill does


# Import On Interaction

## Table of Contents

- [When to Use](#when-to-use)
- [When NOT to Use](#when-not-to-use)
- [Instructions](#instructions)
- [Details](#details)
- [Source](#source)

Your page may contain code or data for a component or resource that isn't immediately necessary. For example, part of the user-interface a user doesn't see unless they click or scroll on parts of the page. This can apply to many kinds of first-party code you author, but this also applies to third-party widgets such as video players or chat widgets where you typically need to click a button to display the main interface.

## When to Use

- Use this when you have third-party widgets (video players, chat widgets) that are costly to load eagerly
- This is helpful for deferring non-critical code until the user actually needs it
- Use this to improve First Input Delay (FID) and Time to Interactive (TTI)

## When NOT to Use

- When the resource is needed immediately on page load and isn't gated behind a user interaction
- When the loading delay after interaction creates a noticeably poor user experience (consider prefetch/preload instead)
- For small modules where the dynamic import overhead exceeds the savings from deferring

## Instructions

- Use dynamic `import()` to load modules on user interaction (click, hover, etc.)
- Implement facades (lightweight placeholders) for heavy third-party embeds
- For first-party code, prefer prefetching over import-on-interaction when possible
- Consider preconnecting to required origins on hover to reduce latency
- Use `React.lazy` with `Suspense` for component-level import-on-interaction in React

## Details

Loading these resources eagerly (i.e right away) can [block the main thread](https://web.dev/long-tasks-devtools/) if they are costly, pushing out how soon a user can interact with more critical parts of a page. This can impact interaction readiness metrics like [First Input Delay](https://web.dev/fid/), [Total Blocking Time](https://web.dev/lighthouse-total-blocking-time/) and [Time to Interactive](https://web.dev/interactive/). Instead of loading these resources immediately, you can load them at a more opportune moment, such as:

- When the user clicks to interact with that component for the first time
- Scrolls the component into view
- or deferring load of that component until the browser is idle (via [requestIdleCallback](https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback)).

The different ways to load resources are, at a high-level:

- Eager - load resource right away (the normal way of loading scripts)
- Lazy (Route-based) - load when a user navigates to a route or component
- Lazy (On interaction) - load when the user clicks UI (e.g Show Chat)
- Lazy (In viewport) - load when the user scrolls towards the component
- [Prefetch](https://web.dev/link-prefetch/) - load prior to needed, but after critical resources are loaded
- [Preload](https://web.dev/preload-critical-assets/) - eagerly, with a greater level of urgency

Lazily importing feature code on interaction is a pattern used in many contexts. One place you may have used it before is Google Docs, where they save loading 500KB of script for the share feature by deferring its load until user-interaction.

Another place where import-on-interaction can be a good fit is loading third-party widgets.

### "Fake" loading third-party UI with a facade

You might be importing a third-party script and have less control over what it renders or when it loads code. One option for implementing load-on-interaction is straight-forward: use a [facade](https://github.com/patrickhulce/third-party-web/blob/10ec0f8f30bbbb73e2de5640cb652a07dd4d7d11/facades.md). A facade is a simple "preview" or "placeholder" for a more costly component where you simulate the basic experience, such as with an image or screenshot. It's terminology we've been using for this idea on the Lighthouse team.

When a user clicks on the "preview" (the facade), the code for the resource is loaded. This limits users needing to pay the experience cost for a feature if they're not going to use it. Similarly, facades can [preconnect](https://web.dev/uses-rel-preconnect/) to necessary resources on hover.

### Video Player Embeds

A good example of a "facade" is the [YouTube Lite Embed](https://github.com/paulirish/lite-youtube-embed) by Paul Irish. This provides a Custom Element which takes a YouTube Video ID and presents a minimal thumbnail and play button. Clicking the element dynamically loads the full YouTube embed code, meaning users who never click play don't pay the cost of fetching and processing it.

A similar technique is used in production on a few Google sites. On Android.com, rather than eagerly loading the YouTube video player embed, a thumbnail with a fake player button is shown to users. When they click it, a modal loads which auto-plays the video using the full-fat YouTube video player embed.

### Authentication

Apps may need to support authentication with a service via a client-side JavaScript SDK. These can occasionally be large with heavy JS execution costs and one might rather not eagerly load them up front if a user isn't going to login. Instead, dynamically import authentication libraries when a user clicks on a "Login" button, keeping the main thread more free during initial load.

### Chat widgets

Calibre app [improved performance of their Intercom-based live chat by 30%](https://calibreapp.com/blog/fast-live-chat) through usage of a similar facade approach. They implemented a "fake" fast loading live chat button using just CSS and HTML, which when clicked would load their Intercom bundles.

[Postmark](https://wildbit.com/blog/2020/09/30/getting-postmark-lighthouse-performance-score-to-100) noted that their Help chat widget was always eagerly loaded, even though it was only occasionally used by customers. The widget would pull in 314KB of script, more than their whole home page. To improve user-experience, they replaced the widget with a fake replica using HTML and CSS, loading the real-thing on click. This change reduced Time to Interactive from 7.7s to 3.7s.

### Others

[Ne-digital](https://medium.com/ne-digital/how-to-reduce-next-js-bundle-size-68f7ac70c375) used a React library for animated scrolling back to the top of a page when a user clicks on a "scroll to top" button. Rather than eagerly loading the react-scroll dependency for this, they load it on interaction with the button, saving ~7KB:

```js
handleScrollToTop() {
  import('react-scroll').then(scroll => {
    scroll.animateScroll.scrollToTop({
    })
  })
}
```

### How do you import-on-interaction?

#### Vanilla JavaScript

In JavaScript, [dynamic import()](https://v8.dev/features/dynamic-import) enables lazy-loading modules and returns a promise and can be quite powerful when applied correctly. Below is an example where dynamic import is used in a button event listener to import the lodash.sortby module and then use it.

```js
const btn = document.querySelector("button");

btn.addEventListener("click", (e) => {
  e.preventDefault();
  import("lodash.sortby")
    .then((module) => module.default)
    .then(sortInput()) // use the imported dependency
    .catch((err) => {
      console.log(err);
    });
});
```

Prior to dynamic import or for use-cases it doesn't fit as well, dynamically injecting scripts into the page using a Promise-based script loader was also an option:

```js
const loginBtn = document.querySelector("#login");

loginBtn.addEventListener("click", () => {
  const loader = new scriptLoader();
  loader
    .load(["//apis.google.com/js/client:platform.js?onload=showLoginScreen"])
    .then(({ length }) => {
      console.log(`${length} scripts loaded!`);
    });
});
```

### React

Let's imagine we have a Chat application which has a `<MessageList>`, `<MessageInput>` and an `<EmojiPicker>` component (powered by [emoji-mart](https://bundlephobia.com/[email protected]), which is 98KB

Related in General