Blog Logo

Instant navigations in Next.js with AI agents

We’ve known for a while how to let an agent write code that passes tests, and even how to build a team of AI agents. What we didn’t know was how to make it write code that feels fast. “Make this navigation instant” is a fuzzy requirement — there’s no Jest assertion for “this feels snappy”. An agent can refactor blindly, eyeball the result, and declare victory without anything actually improving.

Vercel just published how they solved exactly that in v0, their app-generation platform: an agent running in a loop, armed with a deterministic test and a Skill full of proven patterns, that drove production navigation times down to nearly zero. The interesting part isn’t the marketing case study — it’s the technical primitive that makes the loop possible and that you can already use in your own app: the instant() helper shipping with Next.js 16.3.

Diagram of the agent + test loop for instant navigations

The problem: dynamic apps that “feel like a website”

In a server-driven app built on React Server Components, navigating usually means a network roundtrip: you click, nothing happens, the server responds, the page appears. Until now Next.js offered two ways out: statically prerender at build time (impossible for per-user personalized data), or mark links with full prefetch (expensive, and one prefetch request per link — something the team itself admits in the Next.js 16.3 post looked ridiculous: twenty chats in a sidebar, twenty requests).

The 16.3 answer, called Instant Navigations, borrows the SPA trick: instead of prefetching per link, Next.js prefetches a reusable shell per route, cached entirely in the browser. The shell holds everything that can render without waiting on the server: layout, headers, skeletons. On click, the shell shows up instantly and the dynamic content streams in afterwards.

Two flags in next.config.ts turn it on:

import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
  cacheComponents: true,
  partialPrefetching: true,
};

export default nextConfig;

With cacheComponents enabled, whenever a route awaits data on the server you get three choices — and this is the conceptual part that matters:

  • Stream with <Suspense>: the user instantly sees the fallback, content streams in after.
  • Cache with 'use cache': the user instantly sees UI cached from a previous execution.
  • Block: if you’d rather a route waits for the server (a blog post you never want to show as a skeleton), declare it explicitly with export const instant = false in the page or layout.

During development, the Instant Insights panel automatically surfaces which routes aren’t instant — turning a slow navigation into a visible error instead of a vague feeling.

The key piece: a test that asserts “fast”

This is what changes the game for agents. Next.js 16.3 ships a Playwright helper, instant(), imported from @next/playwright, that pauses a navigation and lets you assert which parts of the UI are visible without any network request:

import { expect, test } from '@playwright/test';
import { instant } from '@next/playwright';

test('navigating to chats is instant', async ({ page }) => {
  await page.goto('/');

  await instant(page, async () => {
    await page.getByRole('link', { name: 'Chats' }).click();
    await expect(page.getByTestId('app-title')).toBeVisible();
  });
});

If the expected content was blocked on the network, the test fails. Think about what just happened: a qualitative requirement (“it should feel instant”) became a binary, deterministic assertion. And binary, deterministic assertions are exactly the fuel an agentic loop runs on.

The loop that made v0 instant

The case study is published in Making Navigations Instant in v0. The loop is the classic well-built agent setup, with the three things every serious agent workflow needs: a verifiable goal, guardrails made of proven patterns —the kind you define in AGENTS.MD—, and real production feedback:

  1. Encode the goal as a failing test: the agent writes an instant() test for one specific slow navigation (say, / to /chats).
  2. Apply a fix using the Skill’s patterns: refactor until the test passes.
  3. Retry if it doesn’t: the test is the loop’s exit condition.
  4. Commit the code AND the test: the test stays in CI as a regression guard.

The Skill holding the patterns installs with:

npx skills add vercel/next.js --skill next-cache-components-optimizer

Then you hand the agent a prompt as simple as “Make the navigation from ’/’ to ‘/chats’ instant using the next-cache-components-optimizer skill”. Don’t underestimate the Skill’s role: without it, the agent improvises refactors; with it, the agent applies verified recipes for each blocker type (parallel routes, auth gates, the empty-shell failure mode, responsive skeletons).

What do the fixes look like? In most cases, surprisingly modest: moving dynamic data access below a Suspense boundary so the rest of the page joins the shell:

// before: the await blocks the whole page
export default async function WorkspacePage() {
  const session = await getServerSession();
  const team = await fetchTeam(session);
  return <TeamSettings team={team} />;
}

// after: the shell paints instantly, data streams in
export default function WorkspacePage() {
  return (
    <SettingsPageLayout>
      <SettingsHeader title="Workspace" />
      <Suspense fallback={<WorkspaceSkeleton />}>
        <WorkspaceContent />
      </Suspense>
    </SettingsPageLayout>
  );
}

async function WorkspaceContent() {
  const session = await getServerSession();
  const team = await fetchTeam(session);
  return <TeamSettings team={team} />;
}

Other cases needed bigger refactors, like pulling a blocking dependency out of the root layout and into the components that actually use it. Exactly the kind of change that touches several features, that a human procrastinates on for months, and that a test-driven loop attacks without drama.

The outcome: the homepage (logged in and out), the chat detail page, and every settings subpage went from blocking to instant, with 16 new tests now preventing any future change — human or agent-made — from making them slow again. That last point is my favorite: if agents are going to keep touching your repo, you need verifiers that constrain them, and “don’t break the instant navigations” just became one.

How to enable instant navigations in Next.js 16.3

16.3 shipped as a preview in June (official announcement) and installs from the preview npm tag:

npm install next@preview

If your app isn’t on Cache Components yet, there’s a second Skill, next-cache-components-adoption, that walks the agent through the migration. Both are documented in the AI agents with Next.js guide and the Instant Navigations docs.

My suggested order: enable cacheComponents on a branch, see what Instant Insights flags in development, write (or let the agent write) one instant() test for your most important navigation, and run the loop on that one only. Don’t migrate half the app at once: one green navigation in CI is worth more than twenty promises.

The deeper lesson goes beyond Next.js. The frameworks that win the agent era won’t be the ones that generate the most code — they’ll be the ones that export verifiers: primitives that turn fuzzy qualities — perceived speed, accessibility, UX — into deterministic tests. It’s the same idea as turning screenshots into tests with Claude Code and TestSprite; here the verifier is instant() and the quality is perceived speed.

FAQ

Do I need v0 or Vercel for this?

No. The instant() helper, the flags, and the Skills are all part of open-source Next.js. The v0 story is just the demo; the loop works on any Next.js 16.3 app with Cache Components.

Is this the same as Partial Prerendering?

No. PPR prerenders at build time; here the shell prerendering happens at runtime, while the user browses, and gets cached in the browser. The Vercel team itself stresses the distinction: getting instant navigations is considerably less work than a static PPR migration.

What if I want a route to stay server-bound?

Declare export const instant = false in the page or layout, and the Instant Insights error goes away. You decide which routes are forced to be instant and which aren’t.


What do you think?

Leave your opinion, question or suggestion. Comments are synced with GitHub Discussions .

Back to blog