The Front-end Is Not Only a Page —— Network

The Front-end Is Not Only a Page —— Network

六月 18, 2022

The front-end used to be static pages: It’s returned by an HTTP GET request,
combined with CSS and JavaScript to provide readable content with several
actions. But nowadays, the front-end becomes more rich and interactive. The
single-page application becomes the mainstream to build up our sites. Users can
navigate to pages without reloading, and interact with servers instantly.

Despite the huge improvement in the Web, we don’t think the front-end is as
convenient as a native application since there are still many things that hurt
users’ experience. The technology has evolved but many sites are not keeping up
with the times. Here I’ll talk about some points we should take into
consideration and how to achieve them.

Offline Availability

The biggest difference between traditional web pages and native applications is that native applications don’t depend on the network. In mobile systems, the network requires to apply permission for it, so it’s offline available by default. What are the advantages?

  1. The app shell is preloaded so users can open it fast;
  2. Users can still use some read-only features when offline.

The company I’m working for uses Confluence to create and share documents, but it loads slowly and sometimes gets out of service. I have to remain the frequently used pages open to avoid such incidence, so there are always tens/ hundreds of tabs in my Chrome. It can be very convenient if I can visit these pages offline.

Try to turn off your network and reload this page 😊

Users can visit pages offline with the service worker

This is a feature from Service Worker and we can integrate it easily by Workbox. For most projects, you can use workbox-webpack-plugin to generate a service worker. Or use workbox-build and other workbox modules to generate it manually. This article is not a guidance for Workbox so I’ll skip the detail, you can get more information in the official document. It’s a good idea to enable this feature if your site is contentful.

Data Persistence

When opening a webpage, you may see a blank page with a big loading spinner inside it, despite you having opened this page over and over again. However, it’s not a common representation in native applications. If you open Twitter, it just shows you the feed that already loaded last time and then loads the newest. It improves the Largest Contentful Paint (LCP), which is the most important metric for users. Data persistence is also related to offline availability.

Persisted data is faster than network

There are many APIs to store data in browsers, like LocalStorage, IndexedDB, and FileSystem. Most state management libraries can opt-in to this feature. For example:

For developers who use React Query, there is an experimental plugin persistQueryClient (link) to cache the response from servers.

Prefetching

The network is usually idle after the first scene is loaded, so it’s a waste of time to fetch data until users need it. There is a common strategy in computers that predicts the next operation and executes it in advance to improve the performance (like compilers and the CPU). For example, if a user is scrolling a list, we can prefetch the next page to speed up.

Prefetching saves your time

The rel attribute in the link element allows the browser to load resources in advance. It’s useful for static resources or cross-site navigations.

<link rel="prefetch" href="http://www.example.com/">
<link rel="dns-prefetch" href="http://www.example.com/">
<link rel="preload" href="style.css" as="style">

Service Worker

Service Worker can also precache resources in advance.

import { generateSW } from 'workbox-build'

// precache all html,css,js files in the `build` directory
await generateSW({
  globDirectory: 'build',
  globPatterns: ['**\/*.{html,css,js}'],
  // ...
})

Runtime Prefetching

Link prefetching and Service Worker both load the static resources in your application, but most data received from API servers is fetched at runtime. For this case, we should fetch the data in JavaScript, and manage the preloaded data for the next usage. React Query provides a built-in prefetch feature, data is cached by its queryKey.

// 'todos' is the queryKey
await queryClient.prefetchQuery('todos', fetchTodos)

// `data` is stale-while-revalidate
const { data } = useQuery('todos', fetchTodos)

Optimistic Updating

You may have heard the word “Optimistic Locking” for concurrency control. It leads to a higher throughput when there is only a few conflicts in the system. The optimistic updating assumes the operation always succeeds, change the local state instantly before the server returns a response, and rollback the local state if the request fails.

Twitter uses optimistic updating for "Like"

You can try to throttle the network to “slow 3G” and “offline”. What happens when you click “like” on Twitter?

There are the recipes for optimistic updating:

Users always prefer a fast response for interactions, just update the state optimistically for light actions.

Scroll Restoration

A problem in Github annoys me a lot, you can reproduce it by:

  1. Open Github (make sure you have logged in);
  2. Scroll to the bottom of your feed, and click “More” to load the next page;
  3. Click an item on the second page, and then navigate back;
  4. Items on the second page disappeared.

Every time browsing the feed, I need to open the link in a new tab, or I have to load the pages again. It won’t be a problem in traditional multi-page applications because data is loaded at once and the browser does everything for you. But in single-page applications, you should make sure the list data is preserved when navigated to other routes. In some cases, you may need to set the History.scrollRestoration to handle the restoration manually.

Conclusion

Thanks to the technologies introduced by modern browsers and many awesome libraries, we can build web applications with better user experience easily. All things mentioned above are related to the network, there are many other things to do. I may summarize it next time.

Checklist

- [ ] The application is accessible when offline
- [ ] Users can see the cached main contents before a new one is fetched
- [ ] Data is prefetched before users require it
- [ ] Users can get a reaction instantly after an operation
- [ ] It's safe to navigate back from other routes