Introduction

Make your Svelte app faster and more user-friendly! Find out how to use lazy loading and the Intersection Observer API to speed up page loading and improve user experience.

This article is a Part 3 of the Lazy loading series, where we explore the benefits of lazy loading and how to implement it using the Intersection Observer API. If you don't know what is lazy loading and what is the Intersection Observer API, you need to read the first article in this series, which covers the basics of Lazy loading.

Improving page loading speed is one of my main specializations, and in this article, we will implement lazy loading components in a Svelte app.

If you are only starting to learn JavaScript and want to learn more, then I recommend services like Codecademy where you can start learning JavaScript for free with interacting exercises and quizzes.

Install a fresh Svelte app

Before we get into the implementation of lazy loading in our Svelte app, let's make sure we have a fresh and ready-to-go project to work with. Like with any Vite app, I will run this command:

yarn create vite lazyloading --template svelte

With NPM the command is a bit longer:

npm create vite@latest lazyloading -- --template svelte

This command will create a new Svelte app called “lazyloading” using the Vite bundler. Now, let's navigate to the project directory and run these commands:

yarn && yarn dev

With NPM:

npm i && npm run dev

From the terminal, we can see that our app is running on http://localhost:5176. This is what I see in the browser:

Default Vite with Svelte app

Let's also remove all the unnecessary code that comes with the default Vite scaffold. I will remove src/app.css, src/lib/Counter.svelte and src/assets/ since we don't need them.

Lastly, I will truncate the App.svelte and remove the import './app.css' from the main.js file.

Create Users component

To better see what issue the lazy loading solves, let's create a Users component that will be hidden below the screen. This will simulate a scenario where the user experiences laggy page load instead of just seeing the main content of the page.

Our Users component will be as simple as possible, with just a list of users' names. When the component is mounted, we'll fetch them from an API endpoint using the fetch() method and display them in a list format.

Since this is not a Svelte tutorial, I won't go into details on Svelte specifically. I've created src/lib/Users.svelte with this code:

<script>
    import { onMount } from 'svelte'

    let loading = false
    let users = []

    onMount(() => fetchUsers())

    const fetchUsers = () => {
        loading = true

        fetch('https://jsonplaceholder.typicode.com/users')
            .then(resp => resp.json())
            .then(resp => users = resp)
            .catch(e => console.error(e))
            .finally(() => loading = false)
    }
</script>

{#if loading}
    <h2>Loading... Please wait...</h2>
{:else}
    <ul>
        {#each users as user}
            <li>{user.name}</li>
        {/each}
    </ul>
{/if}

I don't even need to explain what is going on here, it's just basic Svelte. Let's import Users in our App.svelte file and add it to our template, so we can see users in the browser.

<script>
    import Users from './lib/Users.svelte'
</script>

<main>
    <Users />
</main>

<style>
    main {
        max-width: 900px;
        margin: auto;
        padding: 20px;
        font-size: 1.4rem;
    }
</style>

Before previewing the result in the browser, I will also add a 1000 of Lorem ipsum words before the <Users /> component, just to push it down and simulate a scenario where the user has to scroll down to see it. Now, we have a fresh, Svelte app ready to implement lazy loading!

HTML page with long column of text

Create a reusable lazy loading component

Since we want to abstract the lazy loading functionality into a reusable component, let's create a new file src/lib/LazyLoading.svelte. In this file, we'll create a createIntersectionObserver function that returns an instance of the IntersectionObserver, which is a browser API that allows us to observe when an element enters or exits the viewport.

<script>
    let renderComponent = false

    const createIntersectionObserver = () => {
        return new IntersectionObserver(entries => {
            for (const entry of entries) {
                if (entry.isIntersecting) {
                    renderComponent = true
                }
            }
        })
    }
</script>

The main lazy loading logic is implemented, we are looping through all the entries and checking if they are intersecting with the user's viewport. If an entry is intersecting, we change the renderComponent variable to true.

This is the whole LazyLoading.svelte file:

<script>
    import { onMount } from 'svelte'
    import { fade } from 'svelte/transition'

    let rootElement = null
    let renderComponent = false

    onMount(() => {
        if (rootElement) {
            createIntersectionObserver().observe(rootElement)
        }
    })

    const createIntersectionObserver = () => {
        return new IntersectionObserver(entries => {
            for (const entry of entries) {
                if (entry.isIntersecting) {
                    renderComponent = true
                }
            }
        })
    }
</script>

<div bind:this={rootElement}>
    {#if renderComponent}
        <div transition:fade>
            <slot></slot>
        </div>
    {/if}
</div>

Very briefly, when this component is mounted, we are observing the root div of the LazyLoading component, and when it enters the viewport, we set renderComponent to true, which renders our desired component inside a slot.

Now that we have our reusable lazy loading component, let's go back to our App.svelte file and implement it for the Users component. This is how App.svelte looks after changes:

<script>
    import Users from './lib/Users.svelte'
    import LazyLoading from './lib/LazyLoading.svelte'
</script>

<main>
    <p>Lorem ipsum dolor sit amet consectetur adipisicing elit...</p>

    <LazyLoading>
        <Users />
    </LazyLoading>
</main>

<style>
    main {
        max-width: 900px;
        margin: auto;
        padding: 20px;
        font-size: 1.4rem;
    }
</style>

The paragraph is truncated because I want to avoid adding 1000 extra words to my blog post, but you can see the full code and the final result in this GitHub repository.

To make sure that we archived the result, I'm going to open the developer tool in my Firefox browser, go to a “Network” tab, choose the XHR option to see only the AJAX requests, and reload the page.

HTML page with long column of text and opened Firefox dev tools

After refreshing the page, we don't see any request being made to fetch the users. This means that lazy loading was successfully implemented, let's scroll down and see the users.

HTML page with long column of text and opened Firefox dev tools

As soon as we saw the Users component in the browser, we can see that it was loaded lazily, without affecting the initial load time of our app.

Conclusion

In conclusion, lazy loading is a powerful technique that can significantly improve the performance of web applications. In our case, we had only the Users component, but this technique can be applied to any component that takes a long time to load.

As you saw, you don't need extra libraries to implement lazy loading, all it takes is a bit of JavaScript and knowledge about the Intersection Observer API. If you want to know how to implement the same functionality in Vue.js, you can check out my previous article where I explained how to do it.

If you wish any clarification, or you wish to add suggestions for future articles, please, feel free to leave a comment down below, and I'll be happy to discuss it with you.

Keywords: js, framework, lazy, loading, frontend