RouterEdit this page

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:

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>
  );
}