Client-side navigation
After the server has rendered the initial page, the client takes over and handles all subsequent navigation. This can be done by using the client components exported by the @lazarv/react-server/navigation
module or by the functions exposed by the useClient
hook from the @lazarv/react-server/client
module. You can use these functions to navigate to a new page, prefetch a page, or refresh the current page.
It's recommended to use the built-in client components for navigation and refresh. These components will automatically start a React transition to render only the parts of the page that are changing. This will improve the user experience by reducing the amount of time the user has to wait for the page to load.
Functions exposed by the useClient
hook are more low-level and should be used only when you need to start a React transition manually.
You can navigate to a new page by using the Link
client component exported by the @lazarv/react-server/navigation
module.
import { Link } from "@lazarv/react-server/navigation";
export default function Home() {
return (
<div>
<Link to="/about">About</Link>
</div>
);
}
You can enable prefetching of the page by passing the prefetch
prop to the Link
component. This will prefetch the page when the user hovers over the link.
import { Link } from "@lazarv/react-server/client";
export default function Home() {
return (
<div>
<Link to="/about" prefetch>
About
</Link>
</div>
);
}
By default, the prefetched page will be cached indefinitely. You can change this behavior by passing the timeout value in the prefetch
prop to the Link
component. The prefetch
prop accepts a number which represents the number of milliseconds the page will be cached for. After the timeout expires, the page will be removed from the cache and the next time the user hover over the link, the page will be prefetched again.
import { Link } from "@lazarv/react-server/navigation";
export default function Home() {
return (
<div>
<Link to="/about" prefetch={5000}>
About
</Link>
</div>
);
}
To programmatically prefetch a page you can use the prefetch
function returned by the useClient
hook.
import { useClient } from "@lazarv/react-server/client";
export default function Home() {
const { prefetch } = useClient();
return (
<div>
<button onMouseOver={() => prefetch("/about")}>About</button>
</div>
);
}
When the user navigates away from a page the router will cache the last page when you specify a rollback
prop on the Link
component. This will allow the user to navigate back to the previous page without having to wait for the server to render the page again. The provided value represents the number of milliseconds the page will be cached for. After the timeout expires, the page will be removed from the cache and the next time the user navigates back to the page, the page will be rendered again by the server.
import { Link } from "@lazarv/react-server/navigation";
export default function Home() {
return (
<div>
<Link to="/about" rollback={5000}>
About
</Link>
</div>
);
}
To programmatically navigate to a new page you can use the navigate
function returned by the useClient
hook. It's recommended to use navigate
in a React transition to render only the parts of the page that are changing.
import { startTransition } from "react";
import { useClient } from "@lazarv/react-server/client";
export default function Home() {
const { navigate } = useClient();
return (
<div>
<button onClick={() => startTransition(async () => navigate("/about"))}>
About
</button>
</div>
);
}
You can refresh the current page by using the <Refresh>
client component exported by the @lazarv/react-server/navigation
module or the refresh
function returned by the useClient
hook from the @lazarv/react-server/client
module.
import { Refresh } from "@lazarv/react-server/navigation";
export default function Home() {
return (
<div>
<Refresh>Click to refresh!</Refresh>
</div>
);
}
To programmatically refresh the current page you can use the refresh
function returned by the useClient
hook. Again, it's recommended to start a React transition for the page refresh.
import { startTransition } from "react";
import { useClient } from "@lazarv/react-server/client";
export default function Home() {
const { refresh } = useClient();
return (
<div>
<button onClick={() => startTransition(async () => refresh())}>
Refresh
</button>
</div>
);
}
You can navigate to a new page by using the Form
client component exported by the @lazarv/react-server/navigation
module. This component will navigate to the current route on form submit using form data as the query parameters.
import { Form } from "@lazarv/react-server/navigation";
export default function Home() {
return (
<Form>
<input name="name" />
<button type="submit">Submit</button>
</Form>
);
}
You can use the ReactServerComponent
to create an outlet which you can use for sub-page navigation. It's a modern React Server Component driven version of the <iframe>
element. When using a Link
or Refresh
component or programmatically navigating with the navigate
or replace
functions, the client-side router will automatically update the content of the outlet by rendering the result of server-side rendering the outlet. You need to handle the server-side rendering of the outlet yourself using server-side routing and using the useOutlet helper function.
"use client";
import { Link, ReactServerComponent } from "@lazarv/react-server/navigation";
export default function Home() {
return (
<div>
<Link to="/photos/1" target="modal">My photo</Link>
<ReactServerComponent outlet="modal" />
</div>
);
}
There's four types of Link
or Refresh
navigation inside a ReactServerComponent
. Unspecified, target
, local
and root
. When using target
on the Link
or Refresh
component, the navigation will only update the content of the outlet with the same name. When using local
, the navigation will update the content of the parent outlet while URL of the current page is not updated this way. When using root
, the navigation will update the content of the page. When using an unspecified behavior, the navigation will update the content of the page for each outlet if there are any outlets on the page.
"use client";
import { Link, ReactServerComponent } from "@lazarv/react-server/navigation";
export default function Home() {
return (
<div>
<ReactServerComponent outlet="modal">
<Link to="/photos/2" local>My photo</Link>
</ReactServerComponent>
</div>
);
}
When using the ReactServerComponent
with the defer
prop, the content of the outlet will be rendered only when the user navigates to the page that contains the ReactServerComponent
with the defer
prop. This can be useful when you want to defer the rendering of the content of the outlet until the user navigates to the page that contains the ReactServerComponent
with the defer
prop. The content of the outlet will be fetched from the server.
You can also specify the url
prop on the ReactServerComponent
to specify the URL of the content of the outlet. This can be useful when you want to render the content of the outlet from a different URL than the current page.
Using both defer
and url
props on the ReactServerComponent
you can achieve a similar behavior to the iframe
element.
The useClient
hook returns an object with the following properties:
navigate(url: string, options: { outlet?: string; push?: boolean; rollback?: number; signal?: AbortSignal; fallback?: React.ReactNode; Component?: React.ReactNode })
: A function that navigates to a new page or the specified outlet. Therollback
option allows you to cache the current page for a specified amount of time.replace(url: string, options: { outlet?: string; rollback?: number; signal?: AbortSignal; fallback?: React.ReactNode; Component?: React.ReactNode })
: A function that replaces the current page with a new page or the content of the specified outlet. Therollback
option allows you to cache the current page for a specified amount of time.prefetch(url: string, options: { outlet?: string; ttl?: number; signal?: AbortSignal })
: A function that prefetches a page or the content of the specified outlet. Thettl
option allows you to cache the page for a specified amount of time.refresh(outlet?: string, options: { signal?: AbortSignal; fallback?: React.ReactNode; Component?: React.ReactNode })
: A function that refreshes the current page or the content of the specified outlet.abort(outlet?: string, reason?: unknown)
: A function that aborts navigation of the specified outlet. You can use these functions for programmatic navigation.
import { startTransition } from "react";
import { useClient } from "@lazarv/react-server/client";
export default function Home() {
const { navigate, replace, prefetch, refresh } = useClient();
return (
<div>
<button onClick={() => startTransition(async () => navigate("/about"))}>
About
</button>
<button onClick={() => startTransition(async () => replace("/about"))}>
Replace
</button>
<button onMouseOver={() => prefetch("/about")}>Prefetch</button>
<button onClick={() => startTransition(async () => refresh())}>
Refresh
</button>
</div>
);
}
The useOutlet
hook returns a set of functions to interact with the current outlet. These functions are the same as the ones returned by the useClient
hook, but you don't need to specify the outlet
option. In the example below, the navigate
function is scoped to the current outlet, not the entire page. But if the current outlet is the root outlet, the navigate
function will navigate to the entire page.
"use client";
import { useOutlet } from "@lazarv/react-server/client";
export default function Home() {
const { navigate } = useOutlet();
return (
<div>
<button onClick={() => navigate("/about")}>About</button>
</div>
);
}
The fallback
option on the Link
and Refresh
components allows you to specify a fallback component to render until the outlet starts to render the React Server Component. This can be useful when you want to render a loading indicator or a skeleton while the React Server Component is being rendered immediately, without waiting for the React Server Component request to reach the server.
import { Link } from "@lazarv/react-server/navigation";
export default function Home() {
return (
<div>
<Link to="/about" target="content" fallback={<div>Loading...</div>}>About</Link>
</div>
);
}
You can also specify the fallback
option when navigating programmatically with the navigate
or replace
functions.
import { useClient } from "@lazarv/react-server/client";
export default function Home() {
const { navigate } = useClient();
return (
<div>
<button onClick={() => navigate("/about", { fallback: <div>Loading...</div> })}>About</button>
</div>
);
}
The Component
option on the Link
and Refresh
components allows you to specify a React component to render. This can be useful when you want to render a React component directly in the outlet instead of making a request to the server.
import { Link } from "@lazarv/react-server/navigation";
export default function Home() {
return (
<div>
<Link to="/about" target="content" Component={<div>About</div>}>About</Link>
</div>
);
}
You can also specify the Component
option when navigating programmatically with the navigate
or replace
functions.
import { useClient } from "@lazarv/react-server/client";
export default function Home() {
const { navigate } = useClient();
return (
<div>
<button onClick={() => navigate("/about", { Component: <div>About</div> })}>About</button>
</div>
);
}
You can use the revalidate
prop on the Link
, Refresh
and Form
components to control client-side caching of the page or content of the specified outlet. The revalidate
prop can accept a number which represents the number of milliseconds the page or outlet will be cached for the target URL. After the timeout expires, the page will be removed from the cache and the next time the user navigates to the page, the page will be rendered again by fetching the content from the server rendering the React Server Component.
By default, the revalidate
prop is not set, which means that the page or outlet will not be cached at all, and every navigation will fetch the content from the server.
import { Link } from "@lazarv/react-server/navigation";
export default function Home() {
return (
<div>
<Link to="/about" revalidate={5000}>About</Link>
</div>
);
}
You can disable revalidation by passing false
to the revalidate
prop. By disabling revalidation, the page or outlet will be cached indefinitely.
import { Link } from "@lazarv/react-server/navigation";
export default function Home() {
return (
<div>
<Link to="/about" revalidate={false}>About</Link>
</div>
);
}
You can also fully customize the logic for revalidation by passing a function to the revalidate
prop. The function will receive a context object including the outlet
, the target url
and the timestamp
of the cached content. The function should return a boolean indicating whether the content should be revalidated.
"use client";
import { Link } from "@lazarv/react-server/navigation";
export default function Home() {
return (
<div>
<Link to="/about" revalidate={async ({ outlet, url, timestamp }) => {
return Math.random() > 0.5;
}}>About</Link>
</div>
);
}