Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

nextjs-routes

tatethurston55.9kMIT2.2.5TypeScript support: included

Type safe routing for Next.js

link, next, nextjs, route, routing, type safe, typescript

readme

Next.js Routes

Type safe routing for Next.js


nextjs-routes preview gif

What is this? 🧐

nextjs-routes generates type safe routing utilities from your pages and/or app directory.

Notice

If you are using Next.js's App Router you may not need this library. Next.js provides an experimental option to generate typed links. Next.js's option only works for the app directory, and not pages. If you're using the pages directory, or if you're using the app directory and want to use typed objects instead of string interpolation to provide URL parameters and queries, use this library.

Highlights

🦄 Zero config

💨 Types only -- zero runtime (pages directory only)

🛠 No more broken links

🪄 Route autocompletion

🔗 Supports all Next.js route types: static, dynamic, catch all and optional catch all

Installation & Usage 📦

  1. Add this package to your project:

    npm install nextjs-routes
    # or
    yarn add nextjs-routes
    # or
    pnpm add nextjs-routes
  2. Update your next.config.js:

    + const nextRoutes = require("nextjs-routes/config");
    + const withRoutes = nextRoutes();
    
    /** @type {import('next').NextConfig} */
    const nextConfig = {
      reactStrictMode: true,
    };
    
    - module.exports = nextConfig;
    + module.exports = withRoutes(nextConfig);
  3. Start or build your next project:

    npx next dev
    # or
    npx next build

That's it! A @types/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 🙌.

In development, whenever your routes change, your @types/nextjs-routes.d.ts file will automatically update.

If you would prefer to generate the route types file outside of next dev or next build you can also invoke the cli directly: npx nextjs-routes.

Examples 🛠

Link's href prop is now typed based on your application routes:

import Link from "next/link";

<Link
  href={{
    pathname: "/foos/[foo]",
    query: { foo: "bar" },
  }}
>
  Bar
</Link>;

If the route doesn't require any parameters, you can also use a path string:

<Link href="/foo">Foo</Link>

useRouter

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 UrlObject or path string:

push

import { useRouter } from "next/router";

const router = useRouter();
router.push({ pathname: "/foos/[foo]", query: { foo: "test" } });

replace

import { useRouter } from "next/router";

const router = useRouter();
router.replace({ pathname: "/" });

query

import { useRouter } from "next/router";

// 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/router";

const router = useRouter<"/foos/[foo]">();
// query is now typed as `{ foo?: string | undefined }`
router.query;

You can further narrow the query type by checking the router's isReady property.

import { useRouter } from "next/router";

const router = useRouter<"/foos/[foo]">();
// query is typed as `{ foo?: string | undefined }`
router.query;

if (router.isReady) {
  // query is typed as `{ foo: string }`
  router.query;
}

Checking isReady is necessary because of Next's Automatic Static Optimization. The router's query object will be empty for pages that are Automatic Static Optimized. After hydration, Next.js will trigger an update to your application to provide the route parameters in the query object. See Next's documentation for more information. isReady will always return true for server rendered pages.

Route

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";

Pathname

If you want a type for all possible pathnames you can achieve this via Route:

import type { Route } from "nextjs-routes";

// '/' | '/foos/[foo]' | 'other-route' | ...
type Pathname = Route["pathname"];

RoutedQuery

If you want to use the generated Query for a given Route, you can import it from nextjs-routes:

// Query | Query & { foo: string } | ...
import type { RoutedQuery } from "nextjs-routes";

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 the path as a type argument:

// Query & { foo: string }
type FooRouteQuery = RoutedQuery<"/foos/[foo]">;

GetServerSidePropsContext

If you're using getServerSideProps consider using GetServerSidePropsContext from nextjs-routes. This is nearly identical to GetServerSidePropsContext from next, but further narrows types based on nextjs-route's route data.

import type { GetServerSidePropsContext } from "nextjs-routes";

export function getServerSideProps(
  context: GetServerSidePropsContext<"/foos/[foo]">,
) {
  // context.params will include `foo` as a string;
  const { foo } = context.params;
}

GetServerSideProps

If you're using getServerSideProps and TypeScript 4.9 or later, you can combine the satisfies operator with GetServerSideProps from nextjs-routes. This is nearly identical to GetServerSideProps from next, but further narrows types based on nextjs-route's route data.

import type { GetServerSideProps } from "nextjs-routes";

export const getServerSideProps = (async (context) => {
  // context.params will include `foo` as a string;
  const { foo } = context.params;
}) satisfies GetServerSideProps<{}, "/foos/[foo]">;

How does this work? 🤔

nextjs-routes generates types for the pathname and query for every page in your pages directory. The generated types are written to @types/nextjs-routes.d.ts which is automatically referenced by your Next project's tsconfig.json. @types/nextjs-routes.d.ts redefines the types for next/link and next/router and applies the generated route types.

What if I need a runtime?

There are some cases where you may want to generate a type safe path from a Route object, such as when fetching 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:

fetch

import { route } from "nextjs-routes";

fetch(route({ pathname: "/api/foos/[foo]", query: { foo: "foobar" } }));

getServerSideProps

import { route, type GetServerSidePropsContext } from "nextjs-routes";

export function getServerSideProps(context: GetServerSidePropsContext) {
  return {
    redirect: {
      destination: route({ pathname: "/foos/[foo]", query: { foo: "foobar" } }),
      permanent: false,
    },
  };
}

route optionally accepts a trailingSlash:

// api/foos/foobar/
fetch(
  route(
    { pathname: "/api/foos/[foo]", query: { foo: "foobar" } },
    { trailingSlash: true },
  ),
);

Internationalization (i18n)

nextjs-routes refines Link and useRouter based on your Nextjs i18n configuration.

The following next.config.js:

module.exports = withRoutes({
  i18n: {
    defaultLocale: "de-DE",
    locales: ["de-DE", "en-FR", "en-US"],
  },
});

Will type Link and useRouter's locale as 'de-DE' | 'en-FR' | 'en-US'. All other i18n properties (defaultLocale, domainLocales and locales) are also typed.

If you want to use the generated Locale type, you can import it from nextjs-routes:

import { Locale } from "nextjs-routes";

Configuration

You can pass the following options to nextRoutes in your next.config.js:

const nextRoutes = require("nextjs-routes/config");
const withRoutes = nextRoutes({
  outDir: "types",
  cwd: __dirname,
});
  • outDir: The file path indicating the output directory where the generated route types should be written to (e.g.: "types"). The default is to create the file in the same folder as your next.config.js file.

  • cwd: The path to the directory that contains your next.config.js file. This is only necessary for non standard project structures, such as nx. If you are an nx user getting the Could not find a Next.js pages directory error, use cwd: __dirname.

Troubleshooting

Could not find a Next.js pages directory

Non standard project structures, such as those using nx, require that users supply a path to their next.config.js. For nx, this is because nx introduces wrapping layers that invoke commands differently than using the next cli directly.

Solution:

const nextRoutes = require("nextjs-routes/config");
const withRoutes = nextRoutes({
+  cwd: __dirname
});

Contributing 👫

PR's and issues welcomed! For more guidance check out CONTRIBUTING.md

Are you interested in bringing a nextjs-routes like experience to another framework? Open an issue and let's collaborate.

Licensing 📃

See the project's MIT License.

changelog

Changelog

2.2.5

  • Fix CLI invocation on Windows.

2.2.4

  • CLI invocation now reads next.config.js or next.config.mjs.
  • Fix route's handling of query keys whose value is undefined. Fixes #206. Thanks @sleepdotexe!

2.2.3

  • Bug fix: usePathname and useParams were incorrectly resolving to any return types.

2.2.2

  • Adds support for Next.js's app directory. Link accepts either static routes (no url parameters) or a RouteLiteral string, which can be generated by the route helper from this library:

    import { route } from "nextjs-routes";
    
    <Link
      href={route({
        pathname: "/foos/[foo]",
        query: { foo: "bar" },
      })}
    >
      Baz
    </Link>;
  • Add RouteLiteral type. This type represents a string that has been confirmed to be a validated application route and can be passed to Link or useRouter. This is a TypeScript branded type.

    import { RouteLiteral } from "nextjs-routes";

    route returns a RouteLiteral. If you construct a route string you can cast it to a RouteLiteral so that Link and useRouter will accept it:

    const myRoute = `/foos/${foo}` as RouteLiteral

    In general, prefer using the route helper to generate routes.

  • Refine types for usePathname, useRouter and useParams from "next/navigation" to use nextjs-routes generated types.

  • Fix generated routes when using parallel-routes and intercepting-routes.

  • Fix ref type for Link. Previously ref was missing, now it's correctly typed.

2.2.1

  • Fix route generation on Windows. See #187. Thanks @AkanoCA!

2.2.0

  • Add trailingSlash option to route. See #168
  • Fix the generated optional catch all route type. See #183
  • Sort the generated route types lexicographically.

2.1.0

2.0.1

  • Fix GetServerSidePropsContext and GetServerSidePropsResult types. Thanks @po4tion!
  • Change generated file location from nextjs-routes.d.ts to @types/nextjs-routes.d.ts. Thanks @po4tion!

    To preserve the old location, make the following change to your next.config.js:

    const nextRoutes = require("nextjs-routes/config");
    const withRoutes = nextRoutes({
    +  outDir: "",
    });

    Otherwise, delete your old nextjs-routes.d.ts once @types/nextjs-routes.d.ts is generated.

2.0.0

Next's documentation notes the following:

During prerendering, the router's query object will be empty since we do not have query information to provide during this phase. After hydration, Next.js will trigger an update to your application to provide the route parameters in the query object.

See #117 for more context and discussion.

  • Route type generation can now also be generated outside of next build / next dev by using npx nextjs-routes.

1.0.9

  • Enable Link and router methods to only update the route hash. Thanks @sitch!
  • Publish commonjs for route runtime so jest tests don't require transpilation for users. Thanks @panudetjt!
  • Add GetServerSidePropsContext type. Thanks @slhck!

1.0.8

  • Fix type definition import path for nextjs-routes/config so it is visible to tsc.

1.0.7

  • Remove package.json version import. This resolves TypeError[ERR_IMPORT_ASSERTION_TYPE_MISSING]. See #115 for more context.

1.0.6

  • Fix bad publish of 1.0.5

1.0.5

  • The version of nextjs-routes is now included in the generated nextjs-routes.d.ts file.
  • Switch Link to use TypeScript unions instead of function overloading. Function overloading resulted in errors that were difficult for users to understand, and created issues for some use cases.

1.0.4

  • LinkProps now accept path strings for static routes:

    import { LinkProps } from "next/link";
    // previously this would error
    const props: LinkProps = { href: "/foo" };

1.0.3

  • The Route type now includes hash. This enables the following:

    // this will link to /foo#bar
    <Link href={{ pathname: "/foo", hash: "bar" }}>Foo</Link>

1.0.2

  • Link now accepts anchor props:

    <Link href="/dashboard" className="border-indigo-500">
      Dashboard
    </Link>

1.0.1

  • Update NextRouter type to keep query and pathname bound in a union. This allows you to use router from useRouter as an argument to router.push or router.replace:

    const router = useRouter();
    // reload the current page, preserving search parameters
    router.push(router, undefined, { locale: "fr" });

1.0.0

  • This library will now follow semantic versioning.

  • The previously deprecated direct invocation of nextjs-routes via npx nextjs-routes has been removed in favor of automatic regeneration via withRoutes. See #63 for the motivation behind this change or to voice any concerns.

0.1.7

  • Support Next 13 app (beta) directory

  • Add dir option to support non standard NextJS project structures such as Nx:

    // next.config.js
    const withRoutes = require("nextjs-routes/config")({ dir: __dirname });

    Thanks @alexgorbatchev for the contribution!

0.1.6

  • Accept path strings for static routes:

    <Link href="/foo">Foo</Link>

    Thanks @MariaSolOs for the contribution!

  • Use function overloads for Link and router.push and router.replace. This yields better hints for typos in pathnames:

    <Link href={{ pathname: "/foosy/[foo]" }}>Foo</Link>

    Previously: [tsserver 2322] [E] Type '"/foos/[foo]"' is not assignable to type '"/"'.

    Now: Type '"/foosy/[foo]"' is not assignable to type '"/api/hello" | "/bars/[bar]" | "/foos/[foo]" | "/"'. Did you mean '"/foos/[foo]"'? (+2 other overload errors).

0.1.5

  • Export Locale from nextjs-routes.

    import { Locale } from "nextjs-routes";

    Thanks @Asamsig for the contribution!

0.1.4

module.exports = withRoutes({
  i18n: {
    defaultLocale: "de-DE",
    locales: ["de-DE", "en-FR", "en-US"],
  },
});

Will make locale typed as 'de-DE' | 'en-FR' | 'en-US' for Link and useRouter.

0.1.3

  • nextjs-routes pageExtensions has been updated to respect multiple extensions such as .page.tsx. In 0.1.2, only single extensions .tsx were respected. This is now identical behavior to Next.js.

0.1.2

0.1.1

[ skipped ]

0.1.0

This release contains a breaking change, indicated by the minor version bump to 0.1.0. nextjs-routes has not yet reached v1, but will follow semantic versioning once it does. Until then, minor version changes will be used to help flag breaking changes.

  • Breaking change: the withRoutes import path and invocation has changed to better align with the general pattern in the Nextjs plugin ecosystem and to support configuration options, notably the new outDir option. It also now includes an ESM export to support usage in next.config.mjs.
- const { withRoutes } = require("nextjs-routes/next-config.cjs");
+ const withRoutes = require("nextjs-routes/config")();

/** @type {import('next').NextConfig} */
const nextConfig = {
 reactStrictMode: true,
};

module.exports = withRoutes(nextConfig);

Note the import path has changed and the import itself has changed to function that is invoked with any configuration options. This provides better ergonomics for configuration options:

const withRoutes = require("nextjs-routes/config")({ outDir: "types" });
  • The type RoutedQuery has been added to retrieve the Query for a given Route. This is useful as the context type parameter inside getStaticProps and getServerSideProps. Thanks @MariaSolOs for the contribution!

  • withRoutes now accepts outDir as a configuration option to dictate where nextjs-routes.d.ts is generated. Thanks @MariaSolOs for the contribution!

0.0.22

  • Deprecate direct invocation of nextjs-routes in favor of automatic regeneration via withRoutes. See #63 for the motivation behind this change or to voice any concerns.

0.0.21

  • Add route runtime for generating type safe pathnames from a Route object

This can be used to fetch from API routes:

import { route } from "nextjs-routes";

fetch(route({ pathname: "/api/foos/[foo]", query: { foo: "foobar" } }));

Or for type safe redirects from getServerSideProps:

import { route } from "nextjs-routes";

export const getServerSideProps: GetServerSideProps = async (context) => {
  return {
    redirect: {
      destination: route({ pathname: "/foos/[foo]", query: { foo: "foobar" } }),
      permanent: false,
    },
  };
};

0.0.20

  • Move chokidar from devDependencies to dependencies so it's installed automatically.

0.0.19

  • Bug Fix: quote query segments in generated types. See #49 for more context.
  • Bug Fix: don't generate routes for non navigable routes (_error, _app, _document).
  • Bug Fix: don't generate routes for test files that are co-located in pages directory. See #50 for more context.

0.0.18

  • query is now typed as string | string[] | undefined instead of string | undefined.
  • nextjs-routes can now be configured via your next.config.js to automatically regenerate types whenever your routes change:

    // next.config.js
    
    /** @type {import('next').NextConfig} */
    const { withRoutes } = require("nextjs-routes/next-config.cjs");
    
    const nextConfig = {
      reactStrictMode: true,
    };
    
    module.exports = withRoutes(nextConfig);

    This wiring will only run in Next.js' development server (eg npx next dev) and withRoutes will no-op in production.

0.0.17

  • re-export types from next/link and next/router.
  • remove prettier as a peer dependency.
  • enable src/pages for windows users.
  • routes are now generated for routes that start with _. _app, _document, _error and middleware are excluded.
  • gracefully handles missing pages directory and no pages.

0.0.16

  • fixed prettier as an optional peer dependency

0.0.15

  • nextjs-routes no longer adds types to the global type namespace. Previously, Route was available globally. Now, it must be imported:
import type { Route } from "nextjs-routes";
  • query from useRouter is now correctly typed as string | undefined instead of string. If you know the current route, you can supply a type argument to narrow required parameters to string, eg:
  // if you have a page /foos/[foo].ts

  const router = useRouter<"/foos/[foo]">();
  // foo will be typed as a string, because the foo query parameter is required and thus will always be present.
  const { foo } = router.query;

0.0.14

  • Allow passing in query without pathname to change current url parameters.
  • router.query can no longer be undefined.

0.0.13

  • Support search parameters. See #17 for more context.

0.0.12

  • Removed reexports of next/link and next/router.

This means replacing imports of next/link with nextjs-routes/link and next/router with nextjs-routes/router is no longer necessary:

-import Link from "nextjs-routes/link";
+import Link from "next/link";
-import { useRouter } from 'nextjs-routes/router'
+import { useRouter } from 'next/router'
  • Added windows support.