, 2 min read

Lazy Loading of Pagefind

This blog uses Pagefind to provide search on this website. I had written on this here: Pagefind: Searching in Static Sites. In a first step I already removed the ca. 118 kB large pagefind-ui.js. Now I have gone full lazy-loading.

I have added this to my CSS. The highlighted lines are changed to what Grok in a conversation had proposed.

#search-placeholder {
    height: calc(56px * var(--pagefind-ui-scale));
    padding: 0 calc(70px * var(--pagefind-ui-scale)) 0 calc(54px * var(--pagefind-ui-scale));
    background-color: var(--pagefind-ui-background);
    border: var(--pagefind-ui-border-width) solid var(--pagefind-ui-border);
    border-radius: var(--pagefind-ui-border-radius);
    font-size: calc(17px * var(--pagefind-ui-scale));
    font-weight: 500;
    font-family: var(--pagefind-ui-font);
    color: var(--pagefind-ui-text);
    width: 100%;
    box-sizing: border-box;
    appearance: none;
    -webkit-appearance: none;
}

#search-placeholder::placeholder { opacity: 0.2; color: var(--pagefind-ui-text); }

#search-wrapper { position: relative; }
#search-wrapper::before {
    content: "";
    position: absolute;
    top: 50%;
    left: calc(20px * var(--pagefind-ui-scale));
    transform: translateY(-50%);
    width: calc(18px * var(--pagefind-ui-scale));
    height: calc(18px * var(--pagefind-ui-scale));
    background-color: var(--pagefind-ui-text);
    mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2'%3E%3Ccircle cx='11' cy='11' r='8'/%3E%3Cpath d='m21 21-4.35-4.35'/%3E%3C/svg%3E") center / contain no-repeat;
    -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2'%3E%3Ccircle cx='11' cy='11' r='8'/%3E%3Cpath d='m21 21-4.35-4.35'/%3E%3C/svg%3E") center / contain no-repeat;
    pointer-events: none;
    z-index: 1;
}

The HTML and JavaScript look like this.

<div id="search">
    <div id="search-wrapper">
    <input type="search"
        id="search-placeholder"
        class="pagefind-ui__search-input"
        placeholder="Search"
        autocomplete="off"
        autocapitalize="none"
        enterkeyhint="search">
    </div>
</div>

<script>
let pfLoaded = false;

function loadPagefindUI() {
    if (pfLoaded) return;
    pfLoaded = true;

    const link = document.createElement("link");	// 1. Load the CSS dynamically
    link.rel = "stylesheet";
    link.href = "/pagefind/pagefind-ui.css";
    document.head.appendChild(link);

    const script = document.createElement("script");	// Load the JS
    script.src = "/pagefind/pagefind-ui.js";
    script.onload = () => {
        const container = document.getElementById("search");
        container.innerHTML = "";	// this removes the placeholder
        new PagefindUI({ element: "#search", showSubResults: true, autofocus: true });
    };
    document.head.appendChild(script);
}

document.addEventListener("DOMContentLoaded", () => {
    const placeholder = document.getElementById("search-placeholder");
    if (placeholder) {
        placeholder.addEventListener("focus", loadPagefindUI, { once: true });
        placeholder.addEventListener("click", loadPagefindUI, { once: true });
    }
});
</script>

With these changes neither pagefind-ui.css nor pagefind-ui.js are loaded unless the visitor clicks into the search form. This helped to achieve 0 ms total blocking time, and 0.037 ms cumulative layout shift in PageSpeed Insights.