[]previous and next page

Automate it

create-shadow-app codemod next-router-to-shadow rewrites next/link, useRouter and usePathname as far as the Navigation section documents them.

For agentsUpdated

create-shadow-app codemod next-router-to-shadow rewrites next/link, useRouter and usePathname as far as the Navigation section documents them. It edits by source range, prints a diff unless you pass --write, and a second run changes nothing. See Migrate code with codemods for the options.

sh
npx create-shadow-app codemod next-router-to-shadow src
npx create-shadow-app codemod next-router-to-shadow src --write
Next.js The codemod writes
import Link from "next/link" and <Link> A from @shadow-js/router; every <Link> becomes <A> (an A that already exists makes it A as Link)
<Link prefetch={false}>, <Link className="nav"> <A preload={false}>, <A class="nav">: A reads class to join activeClass
useRouter from next/navigation or next/router useRouter from @shadow-js/router
router.push(to) router.navigate(to)
router.replace(to) router.navigate(to, { replace: true })
router.back(), router.forward() router.history.back(), router.history.forward()
router.refresh(), router.prefetch(to) router.invalidate(), router.preload(to)
usePathname() useLocation().pathname, with a note: read it inside JSX or an effect to follow navigation

It warns with the file:line and keeps the Next.js import for what the table does not document: push or replace with an options argument ({ scroll: false }), router.query and the other members of the Pages Router, a router that is passed or destructured, <Link> props without an equivalent (replace, scroll, shallow, passHref, legacyBehavior, locale, as, an object href, and prefetch other than {false}), and the names useSearchParams, useParams, redirect, permanentRedirect, notFound and useSelectedLayoutSegment, with the replacement the table names. This is the exact output for the input shown:

nav.before.tsxtsx
import Link from "next/link";
import { useRouter, usePathname } from "next/navigation";

export function Nav() {
  const router = useRouter();
  const pathname = usePathname();
  return (
    <nav>
      <Link href="/" prefetch={false}>Home</Link>
      <Link href="/docs" className="nav">Docs</Link>
      <button type="button" onClick={() => router.push("/login")}>Log in</button>
      <button type="button" onClick={() => router.replace("/")}>Home</button>
      <button type="button" onClick={() => router.back()}>Back</button>
      <button type="button" onClick={() => router.refresh()}>Reload {pathname}</button>
      <button type="button" onMouseEnter={() => router.prefetch("/docs")}>Docs</button>
    </nav>
  );
}
nav.after.tsxtsx
import { A, useLocation, useRouter } from "@shadow-js/router";

export function Nav() {
  const router = useRouter();
  const pathname = useLocation().pathname;
  return (
    <nav>
      <A href="/" preload={false}>Home</A>
      <A href="/docs" class="nav">Docs</A>
      <button type="button" onClick={() => router.navigate("/login")}>Log in</button>
      <button type="button" onClick={() => router.navigate("/", { replace: true })}>Home</button>
      <button type="button" onClick={() => router.history.back()}>Back</button>
      <button type="button" onClick={() => router.invalidate()}>Reload {pathname}</button>
      <button type="button" onMouseEnter={() => router.preload("/docs")}>Docs</button>
    </nav>
  );
}