Mikko Ohtamaa
Mikko Ohtamaa

Reputation: 83626

SvelteKit loading indicator when a page load time threshold is exceeded

I am using SvelteKit for a website that is mainly server-side rendered pages. The client-side navigation on SvelteKit works beautifully and is super snappy. However, sometimes, when the backend server is called in the route load() function, the response is not instant and maybe several seconds because of the database long-tail latency.

What would be the best way to create a loading indicator that is only displayed if the loading time is not instant (sub 200ms)? I am aware of the generic navigating pattern, but I specifically hope to create a loading indicator that is not triggered unless the page load is not instant.

Upvotes: 18

Views: 13530

Answers (3)

andraaspar
andraaspar

Reputation: 886

In Svelte 5.18.0 + SvelteKit 2.16.0 I had to use:

<!-- +layout.svelte -->

<script>
    import { navigating } from '$app/state'

    let { children } = $props()

    const SHOW_LOADING_DELAY_MS = 300
    let showLoadingRef
    let showLoading = $state(false)

    $effect(() => {
        // Navigation changed, skip any existing wait for showing an indicator.
        clearTimeout(showLoadingRef)
        if (navigating.complete == null) {
            // Navigation completed, hide loading indicator now.
            showLoading = false
        } else {
            // Navigation in progress, show loading indicator in a bit.
            showLoadingRef = setTimeout(() => {
                showLoading = true
            }, SHOW_LOADING_DELAY_MS)
        }
    })
</script>

<h1>The site</h1>
{@render children()}

{#if showLoading}
    <p>Loading...</p>
{/if}

Upvotes: 3

tbdrz
tbdrz

Reputation: 2190

With svelte 5:

<script lang="ts">
  import { navigating } from "$app/stores";

  let showLoader = $state(false);

  $effect(() => {
    if ($navigating) {
      const interval = setInterval(() => {
        showLoader = true;
      }, 300);

      return () => {
        showLoader = false;
        clearInterval(interval);
      };
    }
  });
</script>

Upvotes: 4

Doomd
Doomd

Reputation: 1406

Sveltekit has a store variable called "navigating" which literally indicates if the client is in-between loading pages. From the Sveltekit docs:

navigating is a readable store. When navigating starts, its value is { from, to }, where from and to both mirror the page store value. When navigating finishes, its value reverts to null.

Note, that if your page is pre-rendered, it should be "instant" and thus not show a loading indicator. If not, this "navigating" variable will be not null for as long as it takes for your content to be fetched and/or rendered. If you wanted to, you could make the "Spinner" component show nothing...until after 200ms (using a setTimout timer)...which it sounds like you do want.

Here's a sample implementation from a wrapper component I have used in the past (pardon the tailwindcss syntax...I copied and pasted it). You can customize this idea in __layout.svelte or wrap components or entire "pages" with this {#if $navigating} logic:

<script>
// USE THIS:
import { navigating } from '$app/stores'
// Example spinner/loading component is visible (when $navigating != null):
import Spinner from '$lib/design/Spinner.svelte'
...
</script>

  <main class="py-4">
    <div class="pagePaddingMain flex-col">
      {#if title}
        <h1 class="text-4xl text-center text-cText pb-4 sm:text-5xl">{title}</h1>
      {/if}
      {#if $navigating} <!-- LOOK HERE -->
        <div class="m-8">
          <Spinner />
          <h1 class="ext-3xl text-center text-cText">Fetching {title}...</h1>
        </div>
      {:else}
        <slot />
      {/if}
    </div>
  </main>

That's it. Good luck!

Upvotes: 32

Related Questions