Automate it
create-shadow-app codemod next-router-to-shadow rewrites next/link, useRouter and usePathname as far as the Navigation section documents them.
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.
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:
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>
);
}
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>
);
}