On this page

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.

Import Link for soft navigations between routes:

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>
prefetchShell prefetchFull RSC prefetch
"intent" (default)Viewport (desktop only)Hover/focus (desktop), viewport (mobile)
"viewport"NoneViewport
"none"ViewportOn navigations
{ desktop?, mobile? }Per devicePer device

A static prefetch fetches the shell, or the full RSC 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 to know more.

Redirects

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

// 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(). It keeps the navigation client-side and does not add a history entry:

router.replace("/login")

If your app already has a proxy 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> 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 from/dashboard/reports
When /reports has no Suspense boundary in its static shell, the root fallback replaces the page while it loads.
Root layout
<Suspense>
/dashboardRoot fallback
A closer Suspense boundary in /reports catches the dynamic part, so the page renders and only that part suspends.
Root layout
<Suspense>
/dashboard/reports
<Suspense>
Inner fallback

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:

// 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:

// 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 is prefetched, the page can render without showing a fallback. When only the shell is cached, 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:

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

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

Or to disable it at runtime:

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). If the root layout disables it, child segments must enable it explicitly.

When predictions go wrong

A prediction can be wrong when a proxy 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() and useParams() with the new URL, prefetch the target route, and keep the current page mounted without activating the target route:

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

Use router.push() or router.replace() 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  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, or { scroll: false } on router.push and router.replace to preserve scroll.

useRouter

Access the router imperatively in Client Components:

"use client"

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

const router = useRouter()

router.push

Navigate to a new route:

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

The optional second argument controls how navigations behave:

OptionDefaultBehavior
scrolltrueScroll to the top or to a destination hash.

router.replace

Replace the current route without adding a history entry:

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

Its second argument accepts the same options as router.push.

router.prefetch

Prefetch a route's full RSC payload:

await router.prefetch("/products")

Unlike the default static prefetch from 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:

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.

OptionDefaultEffect
evictfalseDrop 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.
immediatefalseRefetch 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:

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

It accepts the same evict / immediate options as router.revalidate.

router.setCacheHints

Set cache hints for the current segment:

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

stale is in seconds (default: fresh forever, minimum 2 seconds) and tags can be used with router.revalidateTag. See Segment Caching for the full prop reference.

Useful when you need to set these from a Client Component instead of rendering the server-only <CacheHints> component.

router.setRoutePredictionEnabled

Enable or disable route prediction for the current segment's route pattern:

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:

"use client"

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

const pathname = usePathname()
Like with 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. Values update on navigations and raw History API calls:

"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:

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

export default async function ProductsPage() {
  return (
    <>
      <CacheHints stale={300} tags={["products"]} />
      <ProductList />
    </>
  )
}
PropDefaultMeaning
staleFresh foreverSeconds client treats segment as fresh (minimum 2 seconds). Then SWR.
tags[]Client tags for router.revalidateTag. Last write wins.
mustRevalidatefalseOpt 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 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). Client Components can set the same hints with router.setCacheHints.

These hints are independent of the server cache lifetime. 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:

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

<RevalidateSegment focus reconnect interval={30} focusThrottle={5} />
PropDefault
focustrue
reconnecttrue
interval0 (seconds, visible only)
focusThrottle5

<RevalidateSegment> operates only on the client segment cache. It does not invalidate server cache entries.