Optimistic Navigation: Creating the Illusion of Performance

Slow websites drive users away. Even well-optimized sites can suffer from sluggish navigation. Such behavior is often due to poor internet connections or server overload. Achieving consistently fast navigation is challenging. While optimizations like prefetching or AI-driven prerendering can improve the navigation experience, they are often inconsistent and resource-intensive. Optimistic navigation goes completely the opposite way and, instead of preloading, creates the illusion that content is already loaded.

The Problem: Slow Navigation and Unresponsiveness

Picture this: you click a link and await one second for a server response; the UI is frozen, and the website feels broken. In the demo app below, try clicking the links to experience the delay in navigation. The app includes two links: one to the 'Lorem Ipsum' article and another to the 'Home' page.

The demo app above loads the JS chunk of the "/blog/[slug]" page on initial load, reducing the time it takes to open an article for the first time. To achieve the same behavior in Tanstack-Start, add that code to __root.tsx.

useEffect(() => {
    Promise.all([
      router.loadRouteChunk(router.routesByPath['/']),
      router.loadRouteChunk(router.routesByPath['/blog/$postId']),
    ]).catch((err) => new Error(err));
}, []);

Solution: Client-side prediction

To address the problem of sluggish navigation, we can borrow techniques from other fields, like gaming. Client-side prediction is a technique used in game dev; it enhances gameplay by letting the client respond instantly to inputs, reducing the feel of lag. While it introduces temporary desync between client and server, the delay is managed with smoothing techniques to keep the game playable and fair, balancing responsiveness with accuracy (used in Duke Nukem 3D). You can apply this idea to web navigation and achieve a lag-free navigation experience for every user.

In web development, that technique is widely known as an "optimistic UI." Here's the default scenario:

  1. The user performs an action (e.g., clicks a like button).
  2. The UI updates with the predicted state (increase the like count by one and make it active) while fetching the server response.
  3. Once the server response loads, the UI updates to sync with it.

Use it only if you are at least 97% sure that prediction will succeed and the server won't return an error. Don't use it with sensitive or rapidly changing data. When implementing optimistic UI updates, adhere to these guidelines:

  • Synchronize with Server Data: Update the UI to reflect the actual server response.
  • Handle Errors Gracefully: If the server returns an error, revert the UI to its previous state or display an appropriate error message.

Performance illusion

You can achieve the illusion of instant navigation by rendering the opened page optimistically and filling the entire viewport with predicted data. It allows users to interact with the page immediately; this includes actions like adding a product to the cart or scrolling and reading content. I achieved such an illusion in that Tanstack-Start starter; reduce your internet speed to 3G (via network throttling in dev tools) and test it yourself.

That technique hides loading sections by putting them deeper into page view. In most cases users never see loading sections unless they scroll very fast. Even if they do notice, engaging with predicted content beats staring at a blank screen. The demo app below shows optimistic navigation in action.

Optimistic navigation is like a waiter serving you an aperitivo while the main course cooks.

  • Traditional Navigation: Click → Wait → Render.
  • Prefetching: Preload → Click → Render.
  • Optimistic Navigation: Click → Render Predicted Data → Sync with Server.

Recycling Data: Utilizing What’s Already There

In the demo app above, clicking on an article card takes you to the article page, which displays the same thumbnail, title, and description. From a data perspective, on the home page we make a request for a list of articles, and on the article page we request the article detail object.

Typically, a query for a list of entities returns partial data, while a query for an entity object returns its full data. Use the data from the list of entities to render the detail page optimistically. The more data you get in the entity list, the more content can be rendered instantly.

While optimistic navigation doesn’t reduce server response times, it keeps users engaged during delays, potentially lowering bounce rates. A well-designed optimistic navigation can hide 2-10 seconds of network lag from the user, depending on how fast they are scrolling the page.

Building Trust Through Consistency

Optimistic navigation eliminates network delays and lets React render the opened page in 16 milliseconds. It feels seamless, like flipping through a glossy magazine. Predictable, instant responses make users confident about exploring more.

After implementing optimistic navigation, user engagement—measured by metrics such as time on site and number of pages visited—increased by 11.3%.

Based on my six-month study of a media site with 10,000 daily users, after implementing optimistic navigation, engagement increased by 11.3%. Users began to spend more time on the site and open more links.

Exposing Heavy Code

While optimistic navigation enhances user experience, it can also reveal other performance bottlenecks, such as heavy code. After eliminating network delays during navigation, software delays will be more visible. Heavy libraries or computationally expensive components often cause these delays. Consider lazy-loading/refactoring them if the "Interaction to Next Paint" metric takes more than 16 milliseconds in the production build. You can find that metric in Chrome developer tools on the "Performance" tab; it runs on navigation.

Drawbacks of Optimistic Navigation

No solution is flawless. Here are the trade-offs of optimistic navigation:

  • In some cases, it requires returning more data for entity lists.
  • Memory Usage: Storing predicted data can increase memory consumption by 1–2 MB, which is negligible for most devices but could impact ancient hardware.
  • Potential Confusion: If predicted data mismatches the server response, the page re-renders.
  • This can be challenging to implement.

Implementation

During client (soft) navigation, meta-frameworks freeze the interface, waiting for the async function that loads data (getServerSideProps/loader). To implement optimistic navigation, we need to break default meta-framework behavior and move control over route data requests to the client.

  1. Skip calling the data request (getServerSideProps/loader) during client (soft) navigation.
  2. Run those data requests from the client via React Query.
  3. Utilize the placeholderData property of the useQuery hook to pass predicted data.
  4. Handle loading and error state.

To implement the first step in Next.js, I created the `next-query-glue` library, which modifies the Next.js router to allow more control over data fetching. Tanstack-Start is the only meta-framework where such a level of freedom is available without hacks.

If you want to see a complete implementation of optimistic navigation, here are two starters I made:

  1. Optimistic navigation with Tanstack-Start
  2. Optimistic navigation with Next.js pages router and next-query-glue

The example of code below shows how you can optimistically render an article page with react-query .

const ArticlePage: React.FC = () => {
  const { data, isLoading } = usePageData<ArticlePageProps>();

  if (!data || isLoading) {
    return (
      <SkeletonArticlePage />
    );
  }

  return (
    <div>
      <img src={data.thumbnail} alt=""/>
      <h1>{data.title}</h1>
      <h1>{data.description}</h1>
      <ArticleContent />
    </div>
  )
}

const ArticleContent: React.FC<PropsWithChildren> = ({children}) => {
  const { data } = usePageData<ArticlePageProps>();

  if (!data?.content) {
    return (
      <SkeletonText />
    )
  }
  return (
    <div>{data.content}</div>
  )
}

Prefetching vs. Optimistic Navigation

Prefetching is a common alternative to optimistic navigation. It triggers when a link enters the viewport or is hovered over, significantly reducing perceived load times when the user clicks.

Pros:

  • Ease of Use: Supported by most routers and meta-frameworks.
  • Provides excellent UX if the link is loaded before being clicked. Otherwise, the user interface remains frozen until the server responds.

Cons:

  • Inconsistency: Users with slow internet and weak processors may experience delays.
  • Resource Consumption: Drains batteries, wastes internet traffic, and raises hosting costs.
  • Data Staleness: Prefetched content can become outdated once used for rendering.
  • Environmental Impact: Prefetching can lead to unnecessary server requests, which may increase energy consumption and CO₂ emissions at scale.

Conclusion

Modern web development increasingly bets on prefetching content for fast navigation. Optimistic navigation takes a different path, crafting the illusion of instant content loading to elevate the website’s perceived performance. Explore the provided starters above to see optimistic navigation in action.