Next.js 16.3 and Instant Navigations: SPA Speed with the Power of the Server

9 min read

Originally published in Turkish. Read the original →

Next.js 16.3 and Instant Navigations: SPA Speed with the Power of the Server - cover image
Summary

The 'Instant Navigations' feature announced with the Next.js 16.3 preview release cuts the navigation delays of server components down to zero and gives apps an SPA-speed navigation experience.

In modern web development, the tug of war between server-side rendering (SSR) and client-side single page applications (SPAs) has been going on for years. Server-side architectures offer excellent initial load performance, SEO advantages and secure data handling, while client-side SPAs promise that famous "snappy" user experience that reacts instantly when the user clicks a link, with smooth, uninterrupted page transitions.

Although the Next.js team has tried to bridge these two worlds with the React Server Components (RSC) architecture, criticism that navigations feel slow due to the very nature of server components has long stayed on the agenda. Network latency when navigating to a server component meant users had to wait for the new page to load after clicking, without seeing any change on screen in the meantime. This gave apps an old-school "classic website" feel rather than that of a modern mobile app or SPA.

The Next.js 16.3 Preview release arrives with a huge feature set aimed at solving this long-standing problem at its root: Instant Navigations. This new group of features lets Next.js apps reach the speed and responsiveness of client-side SPAs while keeping all the advantages of the server-based model.

Why Does Classic Navigation Feel Slow?

In a traditional server-based app, when the user clicks a link the process works like this:

1. The user clicks the link. 2. The browser or the Next.js router sends a network request to the server asking for the new route's data and component tree. 3. The server queries the database, fetches data from APIs and renders the components. 4. The response travels back to the client over the network. 5. The Next.js router draws the new page on screen.

During the time between steps 2 and 4, nothing changes visually on the client. If the user's internet connection is slow or the server takes 1-2 seconds to fetch data, the user feels as if the app has frozen.

In a client-focused SPA (for example, a classic React project using React Router), on the other hand, the process is different. Since the code is already on the client, the page shell and loading animations (skeleton loaders) appear instantly the moment the user clicks the link. While the data keeps loading in the background, the user sees that the app is working and responding.

Next.js 16.3 combines these two approaches with new optimizations and architectural changes for developers. Page transitions can now start instantly, without waiting for asynchronous server-side work to finish.

First Step: Enabling Cache Components

The first thing you need to do to step into the world of Instant Navigations is to enable the new cacheComponents flag in your Next.js config file:

typescript
// next.config.ts
import type { NextConfig } from 'next';
 
const nextConfig: NextConfig = {
  cacheComponents: true,
  // Your other settings...
};
 
export default nextConfig;

For the past year, the Next.js team has been working to bring the framework back to its roots: a clear structure that is dynamic by default and has no hidden or confusing caching mechanisms. The cacheComponents flag brings these new, simplified and predictable caching behaviors into your project. The Vercel team says this flag will be enabled by default in the next major Next.js release.

Your Choice: Stream, Cache or Block

With cacheComponents enabled, if a route waits for an asynchronous data fetch (await) on the server, you as the developer face three different strategies. With them you control whether the page opens instantly or waits for the server response:

1. Stream (Streaming with React )

If you wrap your data fetching inside a boundary, Next.js starts the page transition the moment the user clicks the link. The user instantly sees the loading UI you defined as the fallback (skeleton, spinner and so on). As data fetching completes on the server, the real UI components stream to the client and replace the loading areas. This makes the page transition feel completely instant and SPA-like.

2. Cache (Caching with 'use cache')

If part of the UI or data can be cached, you can use the 'use cache' directive, which arrived with Next.js 15 and matured with Next.js 16. The moment the user clicks the link, the previously cached, ready-made UI appears instantly on screen. In the background, revalidation can be triggered if fresh data is needed. The wait time for the user drops to zero.

3. Block (export const instant = false)

In some cases, you may never want to show a loading screen or stale cached data during a page transition. For example, on a news portal or in a blog post, you may not want the user to move to the page before the article's title and content are fully ready. For such cases, you can disable "instant" navigation in the relevant page (page.tsx) or layout (layout.tsx) file:

typescript
// app/blog/[slug]/page.tsx
export const instant = false;

export default async function BlogPost({ params }: { params: { slug: string } }) {
  const post = await fetchBlogPost(params.slug);
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </article>
  );
}

When you add the export const instant = false; line, Next.js makes navigations to this page depend on the server. The route transition is "blocked" until all server-side data fetching finishes. The user only sees the transition once the page is fully rendered. This way you decide entirely which pages in your app open instantly and which wait in the traditional way.

Keeping Navigation Fast During Development: Instant Insights and Playwright

During development, it is very easy to accidentally introduce a blocking operation on a page that should be instant. Adding an asynchronous await inside a component can break the instant nature of the entire page transition.

To prevent these kinds of performance regressions, Next.js 16.3 integrates a new panel called Instant Insights into the development server. If navigation slows down or blocks on a route expected to be instant, a warning/error panel automatically appears in the Next.js DevTools UI and points you to the line of code causing the slow navigation.

In addition, a Playwright test helper has been developed so you can guarantee this behavior in your tests too. The instant function imported from the @next/playwright package is used to verify during tests whether a page transition happens instantly, without network delay:

typescript
import { expect, test } from '@playwright/test';
import { instant } from '@next/playwright';
 
test('product title should be visible instantly', async ({ page }) => {
  await page.goto('/products/shoes');
 
  // Test the instant state without waiting for the network request to complete
  await instant(page, async () => {
    await page.click('a[href="/products/hats"]');
    await expect(page.locator('h1')).toContainText('Baseball Cap');
    await expect(page.getByText('Checking stock...')).toBeVisible();
  });
 
  // Test the final state after the network request completes
  await expect(page.getByText('12 in stock')).toBeVisible();
});

In the test example above, the code inside the instant block verifies what is visible on the client the moment the link is clicked, before the network response has come back from the server. If the "Baseball Cap" heading and the "Checking stock..." fallback text do not appear on screen immediately when the link is clicked, the test fails. This way you can make sure your app's SPA-like feel has not been broken.

A Revolution in Prefetching: Partial Prefetching (PPR)

There are two main obstacles to overcome for navigations to feel instant:

1. The network distance between client and server (latency). 2. The time the server needs to prepare the response.

With Streaming and Caching we can overcome the second obstacle (server-side wait time). But how do we minimize network latency?

In earlier versions, Next.js followed an aggressive prefetch policy to solve this problem. For every component on screen, a separate prefetch request was sent to the server in the background. If a page had 20 different chat links (for example, in a messaging app's sidebar), Next.js fired a separate prefetch request for each of those 20 links. This created significant network load and server cost, especially on mobile devices and high-traffic sites. Developers were rightly complaining about seeing dozens of requests pointing to the same route in the Network tab.

To solve this, Next.js 16.3 borrows a technique used by modern SPAs: Partial Prefetching (PPR).

Instead of making a separate prefetch request for each link, Next.js now prepares and prefetches a single shared "loading shell" for each unique route. For example, if you have 20 different links such as /chat/1, /chat/2, /chat/3, Next.js downloads the static loading template (shell) of the /chat/[id] route only once and keeps it in client memory. Whichever of the 20 links the user clicks, the client instantly renders this shared shell. This new route-based approach both reduces network traffic dramatically and gives the app excellent speed.

To try Partial Prefetching, just add this setting to your Next.js config:

typescript
// next.config.ts
import type { NextConfig } from 'next';
 
const nextConfig: NextConfig = {
  cacheComponents: true,
  partialPrefetching: true, // PPR enabled
};
 
export default nextConfig;

This shared-shell-based prefetching also lays great groundwork for future offline support. Even if the internet connection drops momentarily, the client will be able to visually transition between pages thanks to the shells it already holds.

Visually Inspecting Shells with the Navigation Inspector

During development, it can be hard to understand which components are part of the static shell and which are loaded dynamically from the server. The Navigation Inspector added to the Next.js 16.3 DevTools makes this process fully visible.

When you open this tool on the development server, you can pause navigation when you click a link. Next.js then shows you only the prefetched static shell (that is, the first screen the user will see within a tenth of a second of clicking). When you press the "Resume" button, the network request completes and the dynamic data appears on screen. This visual inspector makes it extremely easy to analyze how your app will appear to the user in the first second.

By default, Partial Prefetching prefetches only the route's basic static shell to keep network traffic low. In some critical cases, however, you may want not only the shell but also data or dynamic components to be prefetched. For example, you may want the title and featured image of the "Deals" page in your e-commerce site's main menu to be ready before the user clicks.

In such cases, you can trigger a deeper prefetch by adding to the relevant link. Next.js then also includes in the prefetch payload all components known at build time or marked with 'use cache'.

If you also want data cached at runtime to be prefetched, you can use this configuration in your page component:

typescript
export const prefetch = 'allow-runtime';

With this setting, data that changes dynamically but lives in the cache is also carried to the client with prefetch requests. Page transitions then become completely zero-latency not just at the shell level but at the content level too.

Real-World Performance: The v0 Case Study

The Vercel team actively tested these new Instant Navigations and Partial Prefetching tools on v0, the popular AI design tool they built themselves. Although v0 is a complex web app with heavy client interaction, slow page transitions were hurting the user experience.

The development team integrated the Next.js 16.3 Preview release into v0 and identified blocking routes with the help of the Instant Insights panel. By moving slow data fetches behind boundaries and using 'use cache' in some areas, they managed to bring navigation times down to nearly zero. As a result of these optimizations, the delay between v0 users clicking a link and the route changing dropped dramatically.

In Summary

The Next.js 16.3 Preview release takes web development standards a step further by combining modern server-first web architecture with the smooth navigation speed of SPAs:

* Instant Navigation Experience: Network wait times in page transitions are eliminated entirely with the Stream and Cache methods. * Flexible Control: Where needed, you can return to traditional blocking navigation with export const instant = false;. * Partial Prefetching (PPR): Instead of a separate prefetch request for every link, a single static shell is prefetched per route, minimizing network load. * Advanced Developer Tools: Performance regressions are caught immediately with the Navigation Inspector and the instant() test helper for Playwright.

To try Next.js 16.3 in your project, you can run this command in your terminal:

bash
npm install next@preview

Since it is still in preview, caution is advised when using it in production, but it is exciting to see how fast Next.js will make websites in the future.