Navigation Speed Tests

Guarding Navigation Speed in Tests

A navigation that is instant today goes slow the day someone adds a cookies() read to a shared header or moves a <Suspense> boundary. No suite above notices: the page renders the same text, just later. Next.js 16.3 10,514 offers two guards, the cheaper one in the build. With cacheComponents: true (Enabling Cache Components), deleting the boundary fails next build:

Output of 94
$ npx next build
Error: Route "/products": Next.js encountered uncached or runtime data during prerendering.
`fetch(...)`, `cookies()`, `headers()`, `params`, `searchParams`, or `connection()` accessed
outside of `<Suspense>` prevents the route from being prerendered, blocking the page load.
Ways to fix this:
  - [stream] Provide a placeholder with `<Suspense fallback={...}>` around the data access
  - [cache] For uncached data (`fetch`, database calls): cache the access with `"use cache"`
  - [block] Set `export const instant = false` to allow a blocking route

Restore the boundary and the route reports as ◐ (Partial Prerender). That check is free, so run next build in CI ahead of the suites.

What the build cannot judge is which content belongs in the shell. @next/playwright 16.3.5 adds an instant() helper for that: inside its callback, navigations render only cached and prefetched content, and dynamic data is held back until the callback returns, so assertions are not a race.

e2e/instant.spec.tsJavaScript
import { instant } from '@next/playwright';
test('the products shell appears without waiting for data', async ({ page }) => {
  await page.goto('/');
  await instant(page, async () => {
    await page.getByRole('link', { name: 'Products' }).click();
    await expect(page.getByRole('heading', { name: 'Products' })).toBeVisible();
    await expect(page.getByTestId('loading')).toBeVisible();
  });
  await expect(page.getByText('Enamel mug — $12.00')).toBeVisible();
});

The heading and the placeholder must be instant, the product name only after the callback. Move the boundary up so it wraps the whole page: the build still succeeds, the spec of Playwright still passes, and this one does not.

Output of 95
  x  2 [chromium] › e2e\instant.spec.ts:4:5 › the products shell appears ... (6.2s)
    Error: expect(locator).toBeVisible() failed
    Locator: getByTestId('loading')
    Error: element(s) not found