# Navigations

Soft navigations update the page without a full reload and they're one of the main advantages of using Rebask. Returning to a recently visited route can restore its cached segments and local state. See [Segment Caching](../caching/segment-caching.md#how-the-segment-cache-works).

## `Link`

Import `Link` for soft navigations between routes:

```tsx
import { Link } from "@rebask/core/components/link"

<Link href="/products">Products</Link>
<Link href="/checkout" prefetch="none">Checkout</Link>
<Link href="/products" scroll={false}>Products</Link>
```

| `prefetch`              | Shell prefetch          | Full RSC prefetch                        |
| ----------------------- | ----------------------- | ---------------------------------------- |
| `"intent"` (default)    | Viewport (desktop only) | Hover/focus (desktop), viewport (mobile) |
| `"viewport"`            | None                    | Viewport                                 |
| `"none"`                | Viewport                | On navigations                           |
| `{ desktop?, mobile? }` | Per device              | Per device                               |

A static prefetch fetches the shell, or the [full RSC](../caching/rsc-caching.md) when the route has
no dynamic parts.

On mobile there is no hover, so `"intent"` behaves like `"viewport"`. Visible links prefetch the
full RSC rather than the static shell. Mobile viewports usually show fewer links, which keeps full
RSC prefetch affordable.

`prefetch` only controls `Link`. The router does not prefetch automatically.

> In development, nothing is prefetched ahead of navigations.

Pass `scroll={false}` to preserve the current scroll position. See [Scroll behavior](#scroll-behavior)
to know more.

## Redirects

When the target depends on data read by the page, render a meta refresh:

```tsx
// app/blog/[slug]/page.tsx
import { routeParams } from "@rebask/core/request"

export default async function PostPage() {
  const { slug } = await routeParams<{ slug: string }>()
  const post = await getPost(slug)

  if (!post) {
    const href = `/blog?missing=${encodeURIComponent(slug)}`
    return <meta httpEquiv="refresh" content={`0;url=${href}`} />
  }

  return <Post post={post} />
}
```

React hoists the tag into the document head, so it can be returned directly from the page.

It performs a full document navigation to the target, not a client-side transition. Use it when the
target depends on data read by a Server Component.

For a redirect that starts in a Client Component, call
[`router.replace()`](#userouter-routerreplace). It keeps the navigation client-side and does not add
a history entry:

```tsx
router.replace("/login")
```

If your app already has a [proxy](./proxy.md) and the target can be decided from the request,
returning `Response.redirect()` there also works with soft navigations.

## Suspense and navigations

When a soft navigation reaches an uncached page, Rebask commits the route tree before the page
segment has loaded. The page suspends and the nearest [`<Suspense>`](../rendering/ppr.md) boundary
shows its fallback.
Rebask adds no implicit boundary, so when there is no closer boundary, a root layout fallback
catches the navigation and replaces everything it wraps while the page segment loads. As soon as
the page renders, any closer Suspense boundary it discovers can take over for the dynamic content
inside it:

::navigation-suspense
::

Put Suspense boundaries close to the content that can suspend. Start with the dynamic parts of the
page, then wrap `children` in the closest layout to catch the page segment itself. Avoid making the
root layout the only boundary, because its fallback replaces everything below it.

Inside the page, wrap the dynamic parts in Suspense. Visible links prefetch the page's static
content, so this boundary only handles the content that suspends:

```tsx
// app/blog/page.tsx
import { Suspense } from "react"
import { searchParam } from "@rebask/core/request"

async function Posts() {
	const sort = await searchParam("sort")
	return <PostList sort={sort} />
}

export default function BlogPage() {
	return (
		<main>
			<h1>Blog</h1>
			<Suspense fallback={<PostsSkeleton />}>
				<Posts />
			</Suspense>
		</main>
	)
}
```

To catch the page while it loads, wrap `children` in the closest layout:

```tsx
// app/blog/layout.tsx
import { Suspense } from "react"

export default function BlogLayout({ children }: { children: React.ReactNode }) {
	return <Suspense fallback={<PostsSkeleton />}>{children}</Suspense>
}
```

When the [full RSC payload](../caching/rsc-caching.md#how-rsc-caching-works) is prefetched, the
page can render without showing a fallback. When only the
[shell is cached](../caching/segment-caching.md#shells-and-tails), the shell renders while the
dynamic parts suspends on the closest fallback.

## Route prediction

When the client navigates to a route, the server sends a route pattern such as `/blog/[slug]`
alongside the route tree. The client reuses it for similar paths such as `/blog/2` to skip the
round-trip needed to discover the tree before prefetching segments.

Route prediction is enabled by default. To disable it for the current segment's route and its
descendants:

```tsx
import { RouteHints } from "@rebask/core/components/route-hints"

<>
  <RouteHints prediction={false} />
  {children}
</>
```

Or to disable it at runtime:

```ts
const router = useRouter()
router.setRoutePredictionEnabled(false)
```

A layout's setting cascades to its children, and the deepest explicit setting wins. A child can
re-enable prediction with
[`setRoutePredictionEnabled(true)`](#userouter-routersetroutepredictionenabled). If the root layout
disables it, child segments must enable it explicitly.

### When predictions go wrong

A prediction can be wrong when a [proxy](./proxy.md#rewrites) rewrites the URL to another route. If
the link was prefetched, the client detects the mismatch during prefetch, denies the prediction, and
fetches the authoritative route tree and segments in the background. Denied predictions are not
reused.

## History API

`history.pushState()` and `history.replaceState()` update the URL without navigating. They sync
[`usePathname()`](#usepathname) and [`useParams()`](#useparams) with the new URL, prefetch the
target route, and keep the current page mounted without activating the target route:

```tsx
history.replaceState(null, "", `/chat/${chatId}`)
```

Use [`router.push()`](#userouter-routerpush) or [`router.replace()`](#userouter-routerreplace) to
navigate and render the target route.

## Scroll behavior

By default, router navigations scroll to the top, a hash scrolls to its target. Going back, forward,
and reloading restore the previous position.

That behavior is not part of the [History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API)
by default, it's something Rebask does for router navigations to make scroll position behave like it
does on a normal anchor click.

To prevent Rebask from doing this, set `scroll={false}` on [`Link`](#link), or `{ scroll: false }`
on [`router.push`](#userouter-routerpush) and [`router.replace`](#userouter-routerreplace) to
preserve scroll.

## `useRouter`

Access the router imperatively in Client Components:

```tsx
"use client"

import { useRouter } from "@rebask/core/router"

const router = useRouter()
```

### `router.push`

Navigate to a new route:

```tsx
router.push("/products")
router.push("/products?page=2", { scroll: false })
```

The optional second argument controls how navigations behave:

| Option   | Default | Behavior                                    |
| -------- | ------- | ------------------------------------------- |
| `scroll` | `true`  | Scroll to the top or to a destination hash. |

### `router.replace`

Replace the current route without adding a history entry:

```tsx
router.replace("/login")
router.replace("/products?view=grid", { scroll: false })
```

Its second argument accepts the same options as [`router.push`](#userouter-routerpush).

### `router.prefetch`

Prefetch a route's [full RSC payload](../caching/rsc-caching.md):

```tsx
await router.prefetch("/products")
```

Unlike the default static prefetch from [`Link`](#link), this fetches the full segment.
Use it for links outside the viewport or when the route should be ready before navigations.

### `router.revalidate`

Revalidate the current segment or a specific cached path. Use this after a mutation when the current data is stale but you want to keep showing it while the new data loads:

```tsx
await router.revalidate()
await router.revalidate(undefined, { evict: true })
await router.revalidate("/products", { evict: true, immediate: true })
```

Omitting or passing `undefined` as the first argument picks the current segment.

Passing a URL such as /products revalidates every cached page variant for that pathname. If the URL
includes search params, only variants matching them are revalidated. Layouts are not included
because a URL does not identify which shared layout to invalidate. This only affects routes the
client has already loaded.

| Option      | Default | Effect                                                                                                                                                                                     |
| ----------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `evict`     | `false` | Drop the cached entry and show a loading fallback while refetching. On an active segment, the nearest `<Suspense>` boundary activates. Use when stale data shouldn't be shown to the user. |
| `immediate` | `false` | Refetch right away instead of waiting until the segment is next viewed. Use for inactive segments that need fresh data before the user returns to them.                                    |

### `router.revalidateTag`

Revalidate all cached segments matching the given tag:

```tsx
await router.revalidateTag("products")
await router.revalidateTag("products", { evict: true, immediate: true })
```

It accepts the same `evict` / `immediate` options as [`router.revalidate`](#userouter-routerrevalidate).

### `router.setCacheHints`

Set cache hints for the current segment:

```tsx
router.setCacheHints({ stale: 60, tags: ["products"] })
```

`stale` is in seconds (default: fresh forever, minimum 2 seconds) and `tags` can be used with [`router.revalidateTag`](#userouter-routerrevalidatetag). See [Segment Caching](../caching/segment-caching.md#cache-hints) for the full prop reference.

Useful when you need to set these from a Client Component instead of rendering the server-only
[`<CacheHints>`](#cachehints) component.

### `router.setRoutePredictionEnabled`

Enable or disable [route prediction](#route-prediction) for the current segment's route pattern:

```tsx
router.setRoutePredictionEnabled(false)
```

Disabling it doesn't stop prefetching, but the client will need an extra round-trip to discover the
route tree before prefetching segments.

## `usePathname`

Returns the current URL pathname. Updates on navigations and raw History API calls:

```tsx
"use client"

import { usePathname } from "@rebask/core/router"

const pathname = usePathname()
```

> Like with [`routeParams()`](../server-runtime-apis.md#routeparams), reading the pathname is dynamic
> while the shell renders, so wrap the component in `<Suspense>` to keep the rest of the page in the
> shell.

## `useParams`

Read the current URL's [dynamic parameters](./introduction.md#dynamic-segments). Values update on
navigations and raw History API calls:

```tsx
"use client"

import { useParams } from "@rebask/core/router"

export function ProductLabel() {
  const { id } = useParams<{ id: string }>()
  return <p>Product {id}</p>
}
```

If a component reads a dynamic param that isn’t guaranteed to be provided by every shell it renders
in, place it under `<Suspense>`.

## `<CacheHints>`

Controls how long the client router keeps a segment's RSC payload:

```tsx
import { CacheHints } from "@rebask/core/components/cache-hints"

export default async function ProductsPage() {
  return (
    <>
      <CacheHints stale={300} tags={["products"]} />
      <ProductList />
    </>
  )
}
```

| Prop             | Default       | Meaning                                                                                    |
| ---------------- | ------------- | ------------------------------------------------------------------------------------------ |
| `stale`          | Fresh forever | Seconds client treats segment as fresh (minimum 2 seconds). Then SWR.                      |
| `tags`           | `[]`          | Client tags for [`router.revalidateTag`](#userouter-routerrevalidatetag). Last write wins. |
| `mustRevalidate` | `false`       | Opt out of SWR. Show the nearest `Suspense` fallback while the segment is refetched.       |

`CacheHints` must be rendered in a Server Component because its hints are included in the
[server-rendered RSC payload](../caching/rsc-caching.md) for that segment. The client router reads
them and applies them to the matching page or layout segment (including the 404 content rendered for
[unmatched URLs](../error-handling.md#unmatched-urls)). Client Components can set the same hints
with [`router.setCacheHints`](../routing/navigations.md#userouter-routersetcachehints).

These hints are independent of the [server cache lifetime](../caching/cache-lifetimes.md).
`CacheHints` controls only the matching entries in this browser.

## `<RevalidateSegment>`

Adds browser-side revalidation to the current route segment. By default, it refetches the segment
when the document becomes visible again or the browser reconnects. Set `interval` to also refetch
it periodically while the document is visible:

```tsx
import { RevalidateSegment } from "@rebask/core/components/revalidate-segment"

<RevalidateSegment focus reconnect interval={30} focusThrottle={5} />
```

| Prop            | Default                     |
| --------------- | --------------------------- |
| `focus`         | `true`                      |
| `reconnect`     | `true`                      |
| `interval`      | `0` (seconds, visible only) |
| `focusThrottle` | `5`                         |

`<RevalidateSegment>` operates only on the client segment cache. It does not invalidate
[server cache entries](../caching/introduction.md).
