Type safe routing for Next.js
A code generation tool to make next/link
and next/router
routes type safe with zero runtime overhead. nextjs-routes
scans your pages
directory and generates a nextjs-routes.d.ts
file with type definitions for all your routes.
-
Add this package to your project:
npm install nextjs-routes
oryarn add nextjs-routes
-
Update your
next.config.js
:+ const { withRoutes } = require("nextjs-routes/next-config.cjs"); /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, }; - module.exports = nextConfig; + module.exports = withRoutes(nextConfig);
This wiring will only run in Next.js' development server (eg
npx next dev
) andwithRoutes
will no-op in production. -
Start your next server:
npx next dev
oryarn next dev
That's it! A nextjs-routes.d.ts
file will be generated the first time you start your server. Check this file into version control. next/link
and next/router
type definitions have been augmented to verify your application's routes. No more broken links, and you get route autocompletion π.
Whenever your routes change, your nextjs-routes.d.ts
file will automatically update.
π¦ Zero config
π¨ Types only -- zero runtime
π No more broken links
πͺ Route autocompletion
π Supports all Next.js route types: static, dynamic, catch all and optional catch all
Link
's href
prop is now typed based on your application routes and expects a URL object:
import Link from "next/link";
function Home() {
return (
<Link
href={{
pathname: "/foos/[foo]",
query: { foo: "test" },
}}
>
<a>About us</a>
</Link>
);
}
export default Home;
useRouter
's returned router instance types for push
, replace
and query
are now typed based on your application routes.
Identical to Link
, push
and replace
now expect a URL object:
import { useRouter } from "next/link";
const router = useRouter();
router.push({ pathname: "/foos/[foo]", query: { foo: "test" } });
import { useRouter } from "next/link";
const router = useRouter();
router.replace({ pathname: "/" });
import { useRouter } from "next/link";
// query is typed as a union of all query parameters defined by your application's routes
const { query } = useRouter();
By default, query
will be typed as the union of all possible query parameters defined by your application routes. If you'd like to narrow the type to fewer routes or a single page, you can supply a type argument:
import { useRouter } from "next/link";
// query is now typed as `{ foo: string }`
const { query } = useRouter<"/foos/[foo]">();
If you want to use the generated Route
type in your code, you can import it from nextjs-routes
:
import type { Route } from "nextjs-routes";
nextjs-routes
generates types for the pathname
and query
for every page in your pages
directory. The generated types are written to nextjs-routes.d.ts
which is automatically referenced by your Next project's tsconfig.json
. nextjs-routes.d.ts
redefines the types for next/link
and next/router
and applies the generated route types.
There are some cases where you may want to generate a type safe path from a Route
object, such as when fetch
ing from an API route or serving redirects from getServerSideProps
. These accept strings
instead of the Route
object that Link
and useRouter
accept. Because these do not perform the same string interpolation for dynamic routes, runtime code is required instead of a type only solution.
For these cases, you can use route
from nextjs-routes
:
import { route } from "nextjs-routes";
fetch(route({ pathname: "/api/foos/[foo]", query: { foo: "foobar" } }));
import { route } from "nextjs-routes";
export const getServerSideProps: GetServerSideProps = async (context) => {
return {
redirect: {
destination: route({ pathname: "/foos/[foo]", query: { foo: "foobar" } }),
permanent: false,
},
};
};
PR's and issues welcomed! For more guidance check out CONTRIBUTING.md
See the project's MIT License.