Skip to main content
View all authors

How We Removed Spinners

· 5 min read

Goal

Reduce latency when navigating between workspaces on the Zentry Pass Search page.

info

Here at Zentry Pass we use Next and SWR. The topics discussed in this article will involve Next and SWR and some tricks you can use to improve performance.

Results

To better understand our goal and the results, I recommend visiting our Search page, and clicking around to a few different workspaces and rooms. Each workspace and room is its own page but loads instantly.

Throttling

If you want to take it a step farther, then open up your network tab, enable throttling, and click around again. You will see that pages still load nearly instantly.

note

Images will load slowly while throttlng is enabled.

How we did it

There are 3 tricks we used to accomplish near instant navigation. We'll start with the simpler one, then a less intuitive trick, and finally a trick that greatly improved user experience.

Prefetch Pages

When a user is viewing the Search page on Zentry Pass, it is likely that they are going to click a workspace or room. With this in mind we can have our application prefetch pages by using Next's Link component.

In code that looks like the following.

<Link href={'/workspace/${workspace.id}'}>
// card content
</Link>

When using the Link component, Next will prefetch the page. That means it will load the contents of the page before it is clicked. To see this in action, first navigate to Search, open up your network tab, and clear the current results. Then scroll down the page. You will see additional logs as workspaces come into view. This is Next prefetching the pages.

note

If you have a page with hundreds of links, prefetching can have the opposite effect and degrade performance.

Preload Data

Prefetching in Next is great but only got us half way there. With prefetching, our pages would load incredibly fast but we still had spinners as data was being fetched. You can simulate this by refreshing while on a workspace's page.

We realized that the Search page already has the data needed by the workspace page, so we do not need to refetch it when a user is navigating to it.

One of many cool features SWR offers is key-based caching. So if you make a fetch and then make that same request again shortly after, SWR will return the cached data instead of making another fetch.

However, we cannot rely on SWR's default caching strategy because the cache key while on the Search page is different than while viewing a single workspace. The search page is loading paged data with many workspace results, but the workspace page is only loading a single workspace. We need to iterate over each workspace in the paged data and preload the cache.

Preloading the SWR cache looks something like this in code:

const { mutate } = useSWRConfig();

useEffect(() => {
for (const workspace of workspaces)
mutate(workspace.id, workspace);
}, [workspaces, mutate])

Then retrieving the cached value on the workspace page looks like this:

const workspaceId = params.id;

const { data: workspace, isLoading } = useSWRImmutable(workspaceId, <fetcher>);

if (isLoading) return <Spinner />
// ...

In the code above, if the user is navigating to the workspace page from the search page, then the cached data is used and there is no spinner shown, but if the user is navigating to the workspace page from somewhere else, then there is no cached data and a spinner is shown while the data is fetched.

info

This preloading technique only works with Next's Link component discussed earlier. If not using the Link component, then the SWR cache is not shared between pages and the context is lost. You can also implement a custom SWR store to share between pages if Link is not an option.

useSWRImmutable

You may have noticed useSWRImmutable. The majority of workspace data is unlikely to change during navigation so we tell SWR to not refetch (aka revalidate).

If you do want your pages to revalidate data, a possible solution to still have instant load times is to move preloading the cache when a Link is clicked so the cache data is fresh. You may still have to disable certain revalidation configurations such as revalidateOnMount.

Unintuitive trick

This last trick was a bit unintuitive and didn't directly effect page loading but image loading times while on the Search page. After we implemented prefetching and preloading, we notied the page content loaded instantly but the images would take a very long time to load.

We took a look at the URL of the image and noticed that it was pointing to our Next app. All of the images would be routed through our Next app.

We decided to disable this by adding unoptimized attribute to the Image component like so:

<Image
// brevity
unoptimized
/>

Afterwards, images loaded much faster and the user experience was massively improved. A bit unintuitive that we optimized images by telling it to not optimize.

note

You could also just not use the Image component but it does offer some nice features you will lose out on.