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.
Link
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>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 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:
/dashboard/reports/reports has no Suspense boundary in its static shell, the root fallback replaces the page while it loads.<Suspense>/reports catches the dynamic part, so the page renders and only that part suspends.<Suspense>/reports<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:
// 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:
| 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:
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.
| 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:
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 withrouteParams(), 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 />
</>
)
}| Prop | Default | Meaning |
|---|---|---|
stale | Fresh forever | Seconds client treats segment as fresh (minimum 2 seconds). Then SWR. |
tags | [] | Client tags for router.revalidateTag. 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 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} />| 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.