---
title: Next.js encountered URL data in a Client Component outside of Suspense
kind: insight
---

<div
  style={{
    padding: '1.25rem 1.5rem',
    border: '1px solid var(--ds-gray-400)',
    borderRadius: '12px',
    background: 'var(--ds-background-200)',
    margin: '1.5rem 0 2rem',
    fontSize: '0.95rem',
    lineHeight: '1.6',
  }}
>
  This Insight is part of the [Instant
  Navigations](https://nextjs.org/blog/next-16-3-instant-navigations) feature
  introduced in Next.js 16.3. If you're new to it, start with the [Ensuring
  instant navigations](/docs/app/guides/instant-navigation) guide for an
  overview of what instant navigations are and how Next.js validates them, then
  come back here for the specific fix.
</div>

During [prerendering](/docs/app/glossary#prerendering), a Client Component called a navigation hook ([`usePathname`](/docs/app/api-reference/functions/use-pathname), [`useParams`](/docs/app/api-reference/functions/use-params), [`useSearchParams`](/docs/app/api-reference/functions/use-search-params#prerendering), [`useSelectedLayoutSegment`](/docs/app/api-reference/functions/use-selected-layout-segment), or [`useSelectedLayoutSegments`](/docs/app/api-reference/functions/use-selected-layout-segments)) outside of a [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary. With [Cache Components](/docs/app/api-reference/config/next-config-js/cacheComponents) enabled, Next.js prerenders as much of a route as possible before a request arrives. These hooks read URL data that is not available during prerendering, so the component needs a fallback to include in the [static shell](/docs/app/glossary#static-shell).

The `useSearchParams` hook triggers this error on any prerendered route because search params come from the request URL. The other four hooks trigger it when the route has dynamic params and is rendered per-request.

Server-side request-bound reads ([`cookies()`](/docs/app/api-reference/functions/cookies), [`headers()`](/docs/app/api-reference/functions/headers)) have different fixes. See [Next.js encountered runtime data during prerendering](/docs/messages/blocking-prerender-runtime). For unstable values like [`Math.random()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random) in Client Components, see [Next.js encountered the unstable value Math.random() in a Client Component](/docs/messages/blocking-prerender-random-client).

## Ways to fix this

<FixCardGrid>
  <FixCard
    group="stream"
    title="Wrap in or move into Suspense"
    href="#wrap-in-or-move-into-suspense"
    snippets={[
      { text: '<Suspense fallback={…}>', highlight: true },
      { text: '  <SidebarNav />' },
      { text: '</Suspense>', highlight: true },
    ]}
  />
  <FixCard
    group="block"
    title="Allow blocking route"
    href="#allow-blocking-route"
    snippets={[
      { text: '// page.tsx or layout.tsx' },
      { text: 'export const instant = false', highlight: true },
    ]}
  />
</FixCardGrid>

## Wrap in or move into Suspense

Choose this fix when you want the route to prerender a fallback and replace it with the hook's value at runtime.

### Patterns

#### Wrap the component that calls the hook

Move the hook call into a small Client Component and wrap it in [`<Suspense>`](https://react.dev/reference/react/Suspense). Next.js prerenders the fallback and streams the real value in when it's available.

```jsx filename="app/dashboard/page.js"
import { Suspense } from 'react'
import { Search } from './search'

export default function Page() {
  return (
    <main>
      <h1>Dashboard</h1>
      <Suspense fallback={<p>Loading search...</p>}>
        <Search />
      </Suspense>
    </main>
  )
}
```

```jsx filename="app/dashboard/search.js"
'use client'

import { useSearchParams } from 'next/navigation'

export function Search() {
  const searchParams = useSearchParams()
  return <p>Search: {searchParams.get('q')}</p>
}
```

Learn more: [`useSearchParams` prerendering behavior](/docs/app/api-reference/functions/use-search-params#prerendering)

#### Push the hook read down to the leaf

When the hook is read at the top of the tree but only one piece of UI depends on the value, move the read down. The parent stays prerenderable and only the leaf needs a boundary.

```jsx filename="app/layout.js"
import { Nav } from './nav'

export default function Layout({ children }) {
  return (
    <div>
      <Nav />
      {children}
    </div>
  )
}
```

```jsx filename="app/nav.js"
import { Suspense } from 'react'
import Link from 'next/link'
import { ActiveDot } from './active-dot'

export function Nav() {
  return (
    <nav>
      <Link href="/dashboard">
        Dashboard
        <Suspense>
          <ActiveDot href="/dashboard" />
        </Suspense>
      </Link>
      <Link href="/settings">
        Settings
        <Suspense>
          <ActiveDot href="/settings" />
        </Suspense>
      </Link>
    </nav>
  )
}
```

```jsx filename="app/active-dot.js"
'use client'

import { usePathname } from 'next/navigation'

export function ActiveDot({ href }) {
  const pathname = usePathname()
  const isActive = pathname === href || pathname.startsWith(`${href}/`)
  return isActive ? <span aria-hidden="true"> •</span> : null
}
```

The nav links prerender into the static shell with their final `href` and label. Only the dot suspends, and its empty fallback doesn't shift the layout.

To style the parent `<Link>` itself instead (for example, bolding the active label), have the leaf set `data-active` and read it from the parent with `has-data-active:font-bold`.

Learn more: [`usePathname`](/docs/app/api-reference/functions/use-pathname), [Creating an active link component with `useSelectedLayoutSegment`](/docs/app/api-reference/functions/use-selected-layout-segment#creating-an-active-link-component)

#### Wrap a sibling that uses the hook value

When the hook value drives a non-visual concern (analytics, attribute on a parent), isolate it in a sibling component and wrap that sibling. The visible UI stays in the static shell.

```jsx filename="app/dashboard/page.js"
import { Suspense } from 'react'
import { Header } from './header'
import { TrackPageView } from './track-page-view'

export default function Page() {
  return (
    <>
      <Header />
      <Suspense>
        <TrackPageView />
      </Suspense>
      <DashboardContent />
    </>
  )
}
```

```jsx filename="app/dashboard/track-page-view.js"
'use client'

import { useEffect } from 'react'
import { usePathname } from 'next/navigation'

export function TrackPageView() {
  const pathname = usePathname()
  useEffect(() => {
    track('pageview', { pathname })
  }, [pathname])
  return null
}
```

The sibling renders nothing visible, so an empty fallback is correct. There is no UI to approximate, and the rest of the page stays in the static shell.

### Trade-off

The user sees the fallback on the first paint of the suspended region, then it swaps to the real value once the hook resolves after hydration. Choose a fallback shape that matches the final layout so the swap doesn't cause a layout shift. See [minimizing layout shift](/docs/app/guides/streaming#cls-cumulative-layout-shift).

### Gotchas

- Place the [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary as close to the hook call as possible. Wrapping a large subtree forces the entire subtree into the fallback and loses prerendered content.
- The fallback must be synchronous and deterministic. Do not call [`Math.random()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random), [`Date.now()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now), [`crypto.randomUUID()`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/randomUUID), or [`fetch()`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) inside it. Each one raises a separate [Cache Components](/docs/app/api-reference/config/next-config-js/cacheComponents) error during prerendering.
- Do not pass `{children}` through in the fallback. Child pages may include dynamic reads (for example, `/_not-found` calling [`cookies()`](/docs/app/api-reference/functions/cookies) or [`headers()`](/docs/app/api-reference/functions/headers)) that propagate into what should be a static fallback. Render a placeholder that doesn't include `{children}`.
- A nav rendered in a layout suspends on any page below it that reads URL data the layout's static shell doesn't know. Push the hook read down to the smallest leaf that needs the value so the rest of the nav stays prerendered.
- The static shell does not include the active state. The fallback paints first, then the indicator hydrates and the active style appears. For active-link patterns where the flash is visible, add an [inline script that runs before paint](/docs/app/guides/preventing-flash-before-hydration) to set the active attribute from `location.pathname` so the correct link is styled on the first paint.
- [`usePathname`](/docs/app/api-reference/functions/use-pathname#avoid-hydration-mismatch-with-rewrites) reads the source path on the server when the request was rewritten in `next.config` or middleware, while the browser sees the rewritten path. The active state on a rewritten route resolves to the wrong link on the server and corrects itself on hydration. If your app uses rewrites, defer the read until after mount as the [docs recommend](/docs/app/api-reference/functions/use-pathname#avoid-hydration-mismatch-with-rewrites).

## Allow blocking route

Choose this fix when the route renders per-request and there's no useful static shell. Setting [`instant`](/docs/app/api-reference/file-conventions/route-segment-config/instant) to `false` exempts the segment from instant-navigation validation. The page renders on every request and the navigation blocks until that render completes.

### Patterns

#### Opt the page out

Add the export to the page that triggered the error. Only that route blocks.

```jsx filename="app/dashboard/page.js"
export const instant = false

export default function Page() {
  return <Dashboard />
}
```

Learn more: [Ensuring instant navigations](/docs/app/guides/instant-navigation).

#### Opt the layout out

When the shared layout itself can't ship instantly (it reads URL data of its own that has no meaningful fallback), set [`instant`](/docs/app/api-reference/file-conventions/route-segment-config/instant) to `false` on the layout. This allows that layout segment to block while descendant segments remain independently validated.

```jsx filename="app/dashboard/layout.js"
export const instant = false

export default function DashboardLayout({ children }) {
  return <DashboardShell>{children}</DashboardShell>
}
```

Learn more: [Route segment `instant` config](/docs/app/api-reference/file-conventions/route-segment-config/instant).

Use either pattern when:

- The route needs request-time data high in the tree to decide what to render, so there is no meaningful [static shell](/docs/app/glossary#static-shell) worth showing first.
- You're migrating a route incrementally and want to defer the lifetime decision without changing how the page renders today.

For a client-hook error this is rarely the right answer. The hook reads a small piece of URL data, and a [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary around that read keeps the rest of the route prerendered. Choose [Wrap in or move into Suspense](#wrap-in-or-move-into-suspense) when feasible.

### Trade-off

Navigations to this route are not instant. The user waits for the full server render before any HTML arrives. Use this only when that latency is necessary for the route to function.

### Gotchas

- Setting [`instant`](/docs/app/api-reference/file-conventions/route-segment-config/instant) to `false` opts only the segment that exports it out. Descendant segments are still validated by the global default.
- This export does not disable [prerendering](/docs/app/glossary#prerendering). The route still prerenders if it can. It only disables instant-navigation validation for the route.

## Verifying the fix

After applying a fix, reload the route and confirm the page immediately paints meaningful UI, with any `<Suspense>` fallbacks covering only the regions that stream in. A [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary placed around the whole page body can pass validation with an empty shell, which defeats the point of an instant navigation.

In [`next dev`](/docs/app/api-reference/cli/next#next-dev-options), the error overlay points at the failing component with file paths and line numbers. When working from a build instead, the default [`next build`](/docs/app/api-reference/cli/next#next-build-options) output is more abbreviated. Run `next build --debug-prerender` for full user-frame stack traces and `next build --debug-build-paths /dashboard /settings` to iterate on specific routes.

## Don't want this validation?

Instant-navigation validation runs by default in [Cache Components](/docs/app/api-reference/config/next-config-js/cacheComponents) apps and is what surfaces this error.

- **One segment**: add [`export const instant = false`](/docs/app/api-reference/file-conventions/route-segment-config/instant) to the page or layout file. This opts out the segment itself. Child segments are still validated during client navigations.
- **Entire app**: set [`experimental.instantInsights.validationLevel`](/docs/app/api-reference/file-conventions/route-segment-config/instant#configuring-validation-defaults) to `'manual-warning'` in `next.config`. This limits validation to segments that explicitly export `instant`.

See [Ensuring instant navigations](/docs/app/guides/instant-navigation) for the full model.

## Related Insights

- [Runtime data during prerendering](/docs/messages/blocking-prerender-runtime)
- [Uncached data during prerendering](/docs/messages/blocking-prerender-dynamic)
- [Runtime data in `generateMetadata()`](/docs/messages/blocking-prerender-metadata-runtime)
- [Uncached data in `generateMetadata()`](/docs/messages/blocking-prerender-metadata-dynamic)
- [Runtime data in `generateViewport()`](/docs/messages/blocking-prerender-viewport-runtime)
- [Uncached data in `generateViewport()`](/docs/messages/blocking-prerender-viewport-dynamic)
- [`Math.random()` while prerendering](/docs/messages/blocking-prerender-random)
- [`Math.random()` in a Client Component](/docs/messages/blocking-prerender-random-client)
- [`Date.now()` while prerendering](/docs/messages/blocking-prerender-current-time)
- [`Date.now()` in a Client Component](/docs/messages/blocking-prerender-current-time-client)
- [Crypto APIs while prerendering](/docs/messages/blocking-prerender-crypto)
- [Crypto APIs in a Client Component](/docs/messages/blocking-prerender-crypto-client)
- [Dynamic data during prefetching](/docs/messages/instant-link-prefetch-partial)
- [URL data outside of Suspense](/docs/messages/instant-shell-url-data)
- [Unrendered segment](/docs/messages/instant-unrendered-segment)
