Repository: tatethurston/nextjs-routes
Pull request #87 (2022-10-27 23:36:41)

use function overloads for route types
This yields better TS errors, which we regressed on with the introduction of RouteOrQuery.




Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index af22f60..8f31390 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,19 @@
 # Changelog
 
+## 0.1.6
+
+- `nextjs-routes` now uses function overloads for `Link` and `router.push` and `router.replace`. This yields better hints for typos in pathnames:
+
+  ```tsx
+  <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`.
diff --git a/examples/intl/nextjs-routes.d.ts b/examples/intl/nextjs-routes.d.ts
index dafd129..22d37ed 100644
--- a/examples/intl/nextjs-routes.d.ts
+++ b/examples/intl/nextjs-routes.d.ts
@@ -8,7 +8,9 @@ declare module "nextjs-routes" {
     | { pathname: "/"; query?: Query | undefined }
     | { pathname: "/store"; query?: Query | undefined };
 
-  type Query<Params = {}> = Params & { [key: string]: string | string[] | undefined };
+  type Query<Params = {}> = Params & {
+    [key: string]: string | string[] | undefined;
+  };
 
   type QueryForPathname = {
     [K in Route as K["pathname"]]: Exclude<K["query"], undefined>;
@@ -20,11 +22,11 @@ declare module "nextjs-routes" {
       | "en-US"
     | "fr"
     | "nl-NL";
-  
+
   /**
    * A typesafe utility function for generating paths in your application.
    *
-   * route({ pathname: '/foos/[foo]', query: { foo: 'bar' }}) will produce '/foos/bar'.
+   * route({ pathname: "/foos/[foo]", query: { foo: "bar" }}) will produce "/foos/bar".
    */
   export declare function route(r: Route): string;
 }
@@ -36,16 +38,15 @@ declare module "next/link" {
   import type { PropsWithChildren, MouseEventHandler } from "react";
   export * from "next/dist/client/link";
 
-  type RouteOrQuery = Route | { query?: { [key: string]: string | string[] | undefined } };
+  type Query = { query?: { [key: string]: string | string[] | undefined } };
 
-  export interface LinkProps extends Omit<NextLinkProps, "href" | "locale"> {
-    href: Route["pathname"] | RouteOrQuery;
+  export interface LinkProps<Href extends Route | Query = Route | Query>
+    extends Omit<NextLinkProps, "href" | "locale"> {
+    href: Href;
     locale?: Locale | false;
   }
 
-  declare function Link(
-    props: PropsWithChildren<LinkProps>
-  ): DetailedReactHTMLElement<
+  type LinkReactElement = DetailedReactHTMLElement<
     {
       onMouseEnter?: MouseEventHandler<Element> | undefined;
       onClick: MouseEventHandler;
@@ -55,6 +56,16 @@ declare module "next/link" {
     HTMLElement
   >;
 
+  declare function Link(
+    props: PropsWithChildren<LinkProps<Route>>
+  ): LinkReactElement;
+  declare function Link(
+    props: PropsWithChildren<LinkProps<Route["pathname"]>>
+  ): LinkReactElement;
+  declare function Link(
+    props: PropsWithChildren<LinkProps<Query>>
+  ): LinkReactElement;
+
   export default Link;
 }
 
@@ -67,18 +78,19 @@ declare module "next/router" {
 
   type NextTransitionOptions = NonNullable<Parameters<Router["push"]>[2]>;
 
-  interface TransitionOptions extends Omit<NextTransitionOptions, 'locale'> {
+  interface TransitionOptions extends Omit<NextTransitionOptions, "locale"> {
     locale?: Locale | false;
   };
 
-  type RouteOrQuery = 
-    | Route
-    | { query: { [key: string]: string | string[] | undefined } };
-
   export interface NextRouter<P extends Route["pathname"] = Route["pathname"]>
     extends Omit<
       Router,
-      "push" | "replace" | "locale" | "locales" | "defaultLocale" | "domainLocales"
+      | "push"
+      | "replace"
+      | "locale"
+      | "locales"
+      | "defaultLocale"
+      | "domainLocales"
     > {
     defaultLocale: "en-US";
     domainLocales?: undefined;
@@ -90,13 +102,33 @@ declare module "next/router" {
     ];
     pathname: P;
     push(
-      url: RouteOrQuery,
+      url: Route,
+      as?: string,
+      options?: TransitionOptions
+    ): Promise<boolean>;
+    push(
+      url: Route["pathname"],
+      as?: string,
+      options?: TransitionOptions
+    ): Promise<boolean>;
+    push(
+      url: { query: { [key: string]: string | string[] | undefined } },
       as?: string,
       options?: TransitionOptions
     ): Promise<boolean>;
     query: RoutedQuery<P>;
     replace(
-      url: RouteOrQuery,
+      url: Route,
+      as?: string,
+      options?: TransitionOptions
+    ): Promise<boolean>;
+    replace(
+      url: Route["pathname"],
+      as?: string,
+      options?: TransitionOptions
+    ): Promise<boolean>;
+    replace(
+      url: { query: { [key: string]: string | string[] | undefined } },
       as?: string,
       options?: TransitionOptions
     ): Promise<boolean>;
diff --git a/examples/typescript/types/nextjs-routes.d.ts b/examples/typescript/types/nextjs-routes.d.ts
index 0c73c1f..b0014f8 100644
--- a/examples/typescript/types/nextjs-routes.d.ts
+++ b/examples/typescript/types/nextjs-routes.d.ts
@@ -10,7 +10,9 @@ declare module "nextjs-routes" {
     | { pathname: "/foos/[foo]"; query: Query<{ "foo": string }> }
     | { pathname: "/"; query?: Query | undefined };
 
-  type Query<Params = {}> = Params & { [key: string]: string | string[] | undefined };
+  type Query<Params = {}> = Params & {
+    [key: string]: string | string[] | undefined;
+  };
 
   type QueryForPathname = {
     [K in Route as K["pathname"]]: Exclude<K["query"], undefined>;
@@ -19,11 +21,11 @@ declare module "nextjs-routes" {
   export type RoutedQuery<P extends Route["pathname"]> = QueryForPathname[P];
 
   export type Locale = undefined;
-  
+
   /**
    * A typesafe utility function for generating paths in your application.
    *
-   * route({ pathname: '/foos/[foo]', query: { foo: 'bar' }}) will produce '/foos/bar'.
+   * route({ pathname: "/foos/[foo]", query: { foo: "bar" }}) will produce "/foos/bar".
    */
   export declare function route(r: Route): string;
 }
@@ -35,16 +37,15 @@ declare module "next/link" {
   import type { PropsWithChildren, MouseEventHandler } from "react";
   export * from "next/dist/client/link";
 
-  type RouteOrQuery = Route | { query?: { [key: string]: string | string[] | undefined } };
+  type Query = { query?: { [key: string]: string | string[] | undefined } };
 
-  export interface LinkProps extends Omit<NextLinkProps, "href" | "locale"> {
-    href: Route["pathname"] | RouteOrQuery;
+  export interface LinkProps<Href extends Route | Query = Route | Query>
+    extends Omit<NextLinkProps, "href" | "locale"> {
+    href: Href;
     locale?: false;
   }
 
-  declare function Link(
-    props: PropsWithChildren<LinkProps>
-  ): DetailedReactHTMLElement<
+  type LinkReactElement = DetailedReactHTMLElement<
     {
       onMouseEnter?: MouseEventHandler<Element> | undefined;
       onClick: MouseEventHandler;
@@ -54,6 +55,16 @@ declare module "next/link" {
     HTMLElement
   >;
 
+  declare function Link(
+    props: PropsWithChildren<LinkProps<Route>>
+  ): LinkReactElement;
+  declare function Link(
+    props: PropsWithChildren<LinkProps<Route["pathname"]>>
+  ): LinkReactElement;
+  declare function Link(
+    props: PropsWithChildren<LinkProps<Query>>
+  ): LinkReactElement;
+
   export default Link;
 }
 
@@ -66,18 +77,19 @@ declare module "next/router" {
 
   type NextTransitionOptions = NonNullable<Parameters<Router["push"]>[2]>;
 
-  interface TransitionOptions extends Omit<NextTransitionOptions, 'locale'> {
+  interface TransitionOptions extends Omit<NextTransitionOptions, "locale"> {
     locale?: false;
   };
 
-  type RouteOrQuery = 
-    | Route
-    | { query: { [key: string]: string | string[] | undefined } };
-
   export interface NextRouter<P extends Route["pathname"] = Route["pathname"]>
     extends Omit<
       Router,
-      "push" | "replace" | "locale" | "locales" | "defaultLocale" | "domainLocales"
+      | "push"
+      | "replace"
+      | "locale"
+      | "locales"
+      | "defaultLocale"
+      | "domainLocales"
     > {
     defaultLocale?: undefined;
     domainLocales?: undefined;
@@ -85,13 +97,33 @@ declare module "next/router" {
     locales?: undefined;
     pathname: P;
     push(
-      url: RouteOrQuery,
+      url: Route,
+      as?: string,
+      options?: TransitionOptions
+    ): Promise<boolean>;
+    push(
+      url: Route["pathname"],
+      as?: string,
+      options?: TransitionOptions
+    ): Promise<boolean>;
+    push(
+      url: { query: { [key: string]: string | string[] | undefined } },
       as?: string,
       options?: TransitionOptions
     ): Promise<boolean>;
     query: RoutedQuery<P>;
     replace(
-      url: RouteOrQuery,
+      url: Route,
+      as?: string,
+      options?: TransitionOptions
+    ): Promise<boolean>;
+    replace(
+      url: Route["pathname"],
+      as?: string,
+      options?: TransitionOptions
+    ): Promise<boolean>;
+    replace(
+      url: { query: { [key: string]: string | string[] | undefined } },
       as?: string,
       options?: TransitionOptions
     ): Promise<boolean>;
diff --git a/src/core.ts b/src/core.ts
index 6d50a41..1c462db 100644
--- a/src/core.ts
+++ b/src/core.ts
@@ -97,8 +97,6 @@ function generate(routes: Route[], config: NextJSRoutesOptions): string {
     localeDetection: false,
     locales: [],
   };
-  const unknownQueryParamsType =
-    "{ [key: string]: string | string[] | undefined }";
   return `\
 // THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
 // This file will be automatically regenerated when your Next.js server is running.
@@ -122,7 +120,9 @@ declare module "nextjs-routes" {
             .join("\n    | ")}`
     };
 
-  type Query<Params = {}> = Params & ${unknownQueryParamsType};
+  type Query<Params = {}> = Params & {
+    [key: string]: string | string[] | undefined;
+  };
 
   type QueryForPathname = {
     [K in Route as K["pathname"]]: Exclude<K["query"], undefined>;
@@ -135,11 +135,11 @@ declare module "nextjs-routes" {
       ? "undefined"
       : `\n      | ${i18n.locales.map((x) => `"${x}"`).join("\n    | ")}`
   };
-  
+
   /**
    * A typesafe utility function for generating paths in your application.
    *
-   * route({ pathname: '/foos/[foo]', query: { foo: 'bar' }}) will produce '/foos/bar'.
+   * route({ pathname: "/foos/[foo]", query: { foo: "bar" }}) will produce "/foos/bar".
    */
   export declare function route(r: Route): string;
 }
@@ -151,16 +151,15 @@ declare module "next/link" {
   import type { PropsWithChildren, MouseEventHandler } from "react";
   export * from "next/dist/client/link";
 
-  type RouteOrQuery = Route | { query?: ${unknownQueryParamsType} };
+  type Query = { query?: { [key: string]: string | string[] | undefined } };
 
-  export interface LinkProps extends Omit<NextLinkProps, "href" | "locale"> {
-    href: Route["pathname"] | RouteOrQuery;
+  export interface LinkProps<Href extends Route | Query = Route | Query>
+    extends Omit<NextLinkProps, "href" | "locale"> {
+    href: Href;
     locale?: ${!i18n.locales.length ? "false" : `Locale | false`};
   }
 
-  declare function Link(
-    props: PropsWithChildren<LinkProps>
-  ): DetailedReactHTMLElement<
+  type LinkReactElement = DetailedReactHTMLElement<
     {
       onMouseEnter?: MouseEventHandler<Element> | undefined;
       onClick: MouseEventHandler;
@@ -170,6 +169,16 @@ declare module "next/link" {
     HTMLElement
   >;
 
+  declare function Link(
+    props: PropsWithChildren<LinkProps<Route>>
+  ): LinkReactElement;
+  declare function Link(
+    props: PropsWithChildren<LinkProps<Route["pathname"]>>
+  ): LinkReactElement;
+  declare function Link(
+    props: PropsWithChildren<LinkProps<Query>>
+  ): LinkReactElement;
+
   export default Link;
 }
 
@@ -182,18 +191,19 @@ declare module "next/router" {
 
   type NextTransitionOptions = NonNullable<Parameters<Router["push"]>[2]>;
 
-  interface TransitionOptions extends Omit<NextTransitionOptions, 'locale'> {
+  interface TransitionOptions extends Omit<NextTransitionOptions, "locale"> {
     locale?: ${!i18n.locales.length ? "false" : `Locale | false`};
   };
 
-  type RouteOrQuery = 
-    | Route
-    | { query: ${unknownQueryParamsType} };
-
   export interface NextRouter<P extends Route["pathname"] = Route["pathname"]>
     extends Omit<
       Router,
-      "push" | "replace" | "locale" | "locales" | "defaultLocale" | "domainLocales"
+      | "push"
+      | "replace"
+      | "locale"
+      | "locales"
+      | "defaultLocale"
+      | "domainLocales"
     > {
     defaultLocale${
       i18n.defaultLocale ? `: "${i18n.defaultLocale}"` : "?: undefined"
@@ -207,13 +217,33 @@ declare module "next/router" {
     };
     pathname: P;
     push(
-      url: RouteOrQuery,
+      url: Route,
+      as?: string,
+      options?: TransitionOptions
+    ): Promise<boolean>;
+    push(
+      url: Route["pathname"],
+      as?: string,
+      options?: TransitionOptions
+    ): Promise<boolean>;
+    push(
+      url: { query: { [key: string]: string | string[] | undefined } },
       as?: string,
       options?: TransitionOptions
     ): Promise<boolean>;
     query: RoutedQuery<P>;
     replace(
-      url: RouteOrQuery,
+      url: Route,
+      as?: string,
+      options?: TransitionOptions
+    ): Promise<boolean>;
+    replace(
+      url: Route["pathname"],
+      as?: string,
+      options?: TransitionOptions
+    ): Promise<boolean>;
+    replace(
+      url: { query: { [key: string]: string | string[] | undefined } },
       as?: string,
       options?: TransitionOptions
     ): Promise<boolean>;


Test patch:
diff --git a/e2e/nextjs-routes.d.ts b/e2e/nextjs-routes.d.ts
index 6c84738..d80c22d 100644
--- a/e2e/nextjs-routes.d.ts
+++ b/e2e/nextjs-routes.d.ts
@@ -8,7 +8,9 @@ declare module "nextjs-routes" {
     | { pathname: "/foos/[foo]"; query: Query<{ "foo": string }> }
     | { pathname: "/"; query?: Query | undefined };
 
-  type Query<Params = {}> = Params & { [key: string]: string | string[] | undefined };
+  type Query<Params = {}> = Params & {
+    [key: string]: string | string[] | undefined;
+  };
 
   type QueryForPathname = {
     [K in Route as K["pathname"]]: Exclude<K["query"], undefined>;
@@ -17,11 +19,11 @@ declare module "nextjs-routes" {
   export type RoutedQuery<P extends Route["pathname"]> = QueryForPathname[P];
 
   export type Locale = undefined;
-  
+
   /**
    * A typesafe utility function for generating paths in your application.
    *
-   * route({ pathname: '/foos/[foo]', query: { foo: 'bar' }}) will produce '/foos/bar'.
+   * route({ pathname: "/foos/[foo]", query: { foo: "bar" }}) will produce "/foos/bar".
    */
   export declare function route(r: Route): string;
 }
@@ -33,16 +35,15 @@ declare module "next/link" {
   import type { PropsWithChildren, MouseEventHandler } from "react";
   export * from "next/dist/client/link";
 
-  type RouteOrQuery = Route | { query?: { [key: string]: string | string[] | undefined } };
+  type Query = { query?: { [key: string]: string | string[] | undefined } };
 
-  export interface LinkProps extends Omit<NextLinkProps, "href" | "locale"> {
-    href: Route["pathname"] | RouteOrQuery;
+  export interface LinkProps<Href extends Route | Query = Route | Query>
+    extends Omit<NextLinkProps, "href" | "locale"> {
+    href: Href;
     locale?: false;
   }
 
-  declare function Link(
-    props: PropsWithChildren<LinkProps>
-  ): DetailedReactHTMLElement<
+  type LinkReactElement = DetailedReactHTMLElement<
     {
       onMouseEnter?: MouseEventHandler<Element> | undefined;
       onClick: MouseEventHandler;
@@ -52,6 +53,16 @@ declare module "next/link" {
     HTMLElement
   >;
 
+  declare function Link(
+    props: PropsWithChildren<LinkProps<Route>>
+  ): LinkReactElement;
+  declare function Link(
+    props: PropsWithChildren<LinkProps<Route["pathname"]>>
+  ): LinkReactElement;
+  declare function Link(
+    props: PropsWithChildren<LinkProps<Query>>
+  ): LinkReactElement;
+
   export default Link;
 }
 
@@ -64,18 +75,19 @@ declare module "next/router" {
 
   type NextTransitionOptions = NonNullable<Parameters<Router["push"]>[2]>;
 
-  interface TransitionOptions extends Omit<NextTransitionOptions, 'locale'> {
+  interface TransitionOptions extends Omit<NextTransitionOptions, "locale"> {
     locale?: false;
   };
 
-  type RouteOrQuery = 
-    | Route
-    | { query: { [key: string]: string | string[] | undefined } };
-
   export interface NextRouter<P extends Route["pathname"] = Route["pathname"]>
     extends Omit<
       Router,
-      "push" | "replace" | "locale" | "locales" | "defaultLocale" | "domainLocales"
+      | "push"
+      | "replace"
+      | "locale"
+      | "locales"
+      | "defaultLocale"
+      | "domainLocales"
     > {
     defaultLocale?: undefined;
     domainLocales?: undefined;
@@ -83,13 +95,33 @@ declare module "next/router" {
     locales?: undefined;
     pathname: P;
     push(
-      url: RouteOrQuery,
+      url: Route,
+      as?: string,
+      options?: TransitionOptions
+    ): Promise<boolean>;
+    push(
+      url: Route["pathname"],
+      as?: string,
+      options?: TransitionOptions
+    ): Promise<boolean>;
+    push(
+      url: { query: { [key: string]: string | string[] | undefined } },
       as?: string,
       options?: TransitionOptions
     ): Promise<boolean>;
     query: RoutedQuery<P>;
     replace(
-      url: RouteOrQuery,
+      url: Route,
+      as?: string,
+      options?: TransitionOptions
+    ): Promise<boolean>;
+    replace(
+      url: Route["pathname"],
+      as?: string,
+      options?: TransitionOptions
+    ): Promise<boolean>;
+    replace(
+      url: { query: { [key: string]: string | string[] | undefined } },
       as?: string,
       options?: TransitionOptions
     ): Promise<boolean>;
diff --git a/src/__snapshots__/core.test.ts.snap b/src/__snapshots__/core.test.ts.snap
index 1dc246e..b85eac5 100644
--- a/src/__snapshots__/core.test.ts.snap
+++ b/src/__snapshots__/core.test.ts.snap
@@ -13,7 +13,9 @@ declare module "nextjs-routes" {
   export type Route =
     | { pathname: "/"; query?: Query | undefined };
 
-  type Query<Params = {}> = Params & { [key: string]: string | string[] | undefined };
+  type Query<Params = {}> = Params & {
+    [key: string]: string | string[] | undefined;
+  };
 
   type QueryForPathname = {
     [K in Route as K["pathname"]]: Exclude<K["query"], undefined>;
@@ -25,11 +27,11 @@ declare module "nextjs-routes" {
       | "en-US"
     | "fr"
     | "nl-NL";
-  
+
   /**
    * A typesafe utility function for generating paths in your application.
    *
-   * route({ pathname: '/foos/[foo]', query: { foo: 'bar' }}) will produce '/foos/bar'.
+   * route({ pathname: "/foos/[foo]", query: { foo: "bar" }}) will produce "/foos/bar".
    */
   export declare function route(r: Route): string;
 }
@@ -41,16 +43,15 @@ declare module "next/link" {
   import type { PropsWithChildren, MouseEventHandler } from "react";
   export * from "next/dist/client/link";
 
-  type RouteOrQuery = Route | { query?: { [key: string]: string | string[] | undefined } };
+  type Query = { query?: { [key: string]: string | string[] | undefined } };
 
-  export interface LinkProps extends Omit<NextLinkProps, "href" | "locale"> {
-    href: RouteOrQuery;
+  export interface LinkProps<Href extends Route | Query = Route | Query>
+    extends Omit<NextLinkProps, "href" | "locale"> {
+    href: Href;
     locale?: Locale | false;
   }
 
-  declare function Link(
-    props: PropsWithChildren<LinkProps>
-  ): DetailedReactHTMLElement<
+  type LinkReactElement = DetailedReactHTMLElement<
     {
       onMouseEnter?: MouseEventHandler<Element> | undefined;
       onClick: MouseEventHandler;
@@ -60,6 +61,16 @@ declare module "next/link" {
     HTMLElement
   >;
 
+  declare function Link(
+    props: PropsWithChildren<LinkProps<Route>>
+  ): LinkReactElement;
+  declare function Link(
+    props: PropsWithChildren<LinkProps<Route["pathname"]>>
+  ): LinkReactElement;
+  declare function Link(
+    props: PropsWithChildren<LinkProps<Query>>
+  ): LinkReactElement;
+
   export default Link;
 }
 
@@ -72,18 +83,19 @@ declare module "next/router" {
 
   type NextTransitionOptions = NonNullable<Parameters<Router["push"]>[2]>;
 
-  interface TransitionOptions extends Omit<NextTransitionOptions, 'locale'> {
+  interface TransitionOptions extends Omit<NextTransitionOptions, "locale"> {
     locale?: Locale | false;
   };
 
-  type RouteOrQuery = 
-    | Route
-    | { query: { [key: string]: string | string[] | undefined } };
-
   export interface NextRouter<P extends Route["pathname"] = Route["pathname"]>
     extends Omit<
       Router,
-      "push" | "replace" | "locale" | "locales" | "defaultLocale" | "domainLocales"
+      | "push"
+      | "replace"
+      | "locale"
+      | "locales"
+      | "defaultLocale"
+      | "domainLocales"
     > {
     defaultLocale: "en-US";
     domainLocales: [
@@ -108,13 +120,33 @@ declare module "next/router" {
     ];
     pathname: P;
     push(
-      url: RouteOrQuery,
+      url: Route,
+      as?: string,
+      options?: TransitionOptions
+    ): Promise<boolean>;
+    push(
+      url: Route["pathname"],
+      as?: string,
+      options?: TransitionOptions
+    ): Promise<boolean>;
+    push(
+      url: { query: { [key: string]: string | string[] | undefined } },
       as?: string,
       options?: TransitionOptions
     ): Promise<boolean>;
     query: RoutedQuery<P>;
     replace(
-      url: RouteOrQuery,
+      url: Route,
+      as?: string,
+      options?: TransitionOptions
+    ): Promise<boolean>;
+    replace(
+      url: Route["pathname"],
+      as?: string,
+      options?: TransitionOptions
+    ): Promise<boolean>;
+    replace(
+      url: { query: { [key: string]: string | string[] | undefined } },
       as?: string,
       options?: TransitionOptions
     ): Promise<boolean>;
@@ -141,7 +173,9 @@ declare module "nextjs-routes" {
   export type Route =
     | { pathname: "/404"; query?: Query | undefined };
 
-  type Query<Params = {}> = Params & { [key: string]: string | string[] | undefined };
+  type Query<Params = {}> = Params & {
+    [key: string]: string | string[] | undefined;
+  };
 
   type QueryForPathname = {
     [K in Route as K["pathname"]]: Exclude<K["query"], undefined>;
@@ -150,11 +184,11 @@ declare module "nextjs-routes" {
   export type RoutedQuery<P extends Route["pathname"]> = QueryForPathname[P];
 
   export type Locale = undefined;
-  
+
   /**
    * A typesafe utility function for generating paths in your application.
    *
-   * route({ pathname: '/foos/[foo]', query: { foo: 'bar' }}) will produce '/foos/bar'.
+   * route({ pathname: "/foos/[foo]", query: { foo: "bar" }}) will produce "/foos/bar".
    */
   export declare function route(r: Route): string;
 }
@@ -166,16 +200,15 @@ declare module "next/link" {
   import type { PropsWithChildren, MouseEventHandler } from "react";
   export * from "next/dist/client/link";
 
-  type RouteOrQuery = Route | { query?: { [key: string]: string | string[] | undefined } };
+  type Query = { query?: { [key: string]: string | string[] | undefined } };
 
-  export interface LinkProps extends Omit<NextLinkProps, "href" | "locale"> {
-    href: RouteOrQuery;
+  export interface LinkProps<Href extends Route | Query = Route | Query>
+    extends Omit<NextLinkProps, "href" | "locale"> {
+    href: Href;
     locale?: false;
   }
 
-  declare function Link(
-    props: PropsWithChildren<LinkProps>
-  ): DetailedReactHTMLElement<
+  type LinkReactElement = DetailedReactHTMLElement<
     {
       onMouseEnter?: MouseEventHandler<Element> | undefined;
       onClick: MouseEventHandler;
@@ -185,6 +218,16 @@ declare module "next/link" {
     HTMLElement
   >;
 
+  declare function Link(
+    props: PropsWithChildren<LinkProps<Route>>
+  ): LinkReactElement;
+  declare function Link(
+    props: PropsWithChildren<LinkProps<Route["pathname"]>>
+  ): LinkReactElement;
+  declare function Link(
+    props: PropsWithChildren<LinkProps<Query>>
+  ): LinkReactElement;
+
   export default Link;
 }
 
@@ -197,18 +240,19 @@ declare module "next/router" {
 
   type NextTransitionOptions = NonNullable<Parameters<Router["push"]>[2]>;
 
-  interface TransitionOptions extends Omit<NextTransitionOptions, 'locale'> {
+  interface TransitionOptions extends Omit<NextTransitionOptions, "locale"> {
     locale?: false;
   };
 
-  type RouteOrQuery = 
-    | Route
-    | { query: { [key: string]: string | string[] | undefined } };
-
   export interface NextRouter<P extends Route["pathname"] = Route["pathname"]>
     extends Omit<
       Router,
-      "push" | "replace" | "locale" | "locales" | "defaultLocale" | "domainLocales"
+      | "push"
+      | "replace"
+      | "locale"
+      | "locales"
+      | "defaultLocale"
+      | "domainLocales"
     > {
     defaultLocale?: undefined;
     domainLocales?: undefined;
@@ -216,13 +260,33 @@ declare module "next/router" {
     locales?: undefined;
     pathname: P;
     push(
-      url: RouteOrQuery,
+      url: Route,
+      as?: string,
+      options?: TransitionOptions
+    ): Promise<boolean>;
+    push(
+      url: Route["pathname"],
+      as?: string,
+      options?: TransitionOptions
+    ): Promise<boolean>;
+    push(
+      url: { query: { [key: string]: string | string[] | undefined } },
       as?: string,
       options?: TransitionOptions
     ): Promise<boolean>;
     query: RoutedQuery<P>;
     replace(
-      url: RouteOrQuery,
+      url: Route,
+      as?: string,
+      options?: TransitionOptions
+    ): Promise<boolean>;
+    replace(
+      url: Route["pathname"],
+      as?: string,
+      options?: TransitionOptions
+    ): Promise<boolean>;
+    replace(
+      url: { query: { [key: string]: string | string[] | undefined } },
       as?: string,
       options?: TransitionOptions
     ): Promise<boolean>;
@@ -251,7 +315,9 @@ declare module "nextjs-routes" {
     | { pathname: "/"; query?: Query | undefined }
     | { pathname: "/foo"; query?: Query | undefined };
 
-  type Query<Params = {}> = Params & { [key: string]: string | string[] | undefined };
+  type Query<Params = {}> = Params & {
+    [key: string]: string | string[] | undefined;
+  };
 
   type QueryForPathname = {
     [K in Route as K["pathname"]]: Exclude<K["query"], undefined>;
@@ -260,11 +326,11 @@ declare module "nextjs-routes" {
   export type RoutedQuery<P extends Route["pathname"]> = QueryForPathname[P];
 
   export type Locale = undefined;
-  
+
   /**
    * A typesafe utility function for generating paths in your application.
    *
-   * route({ pathname: '/foos/[foo]', query: { foo: 'bar' }}) will produce '/foos/bar'.
+   * route({ pathname: "/foos/[foo]", query: { foo: "bar" }}) will produce "/foos/bar".
    */
   export declare function route(r: Route): string;
 }
@@ -276,16 +342,15 @@ declare module "next/link" {
   import type { PropsWithChildren, MouseEventHandler } from "react";
   export * from "next/dist/client/link";
 
-  type RouteOrQuery = Route | { query?: { [key: string]: string | string[] | undefined } };
+  type Query = { query?: { [key: string]: string | string[] | undefined } };
 
-  export interface LinkProps extends Omit<NextLinkProps, "href" | "locale"> {
-    href: RouteOrQuery;
+  export interface LinkProps<Href extends Route | Query = Route | Query>
+    extends Omit<NextLinkProps, "href" | "locale"> {
+    href: Href;
     locale?: false;
   }
 
-  declare function Link(
-    props: PropsWithChildren<LinkProps>
-  ): DetailedReactHTMLElement<
+  type LinkReactElement = DetailedReactHTMLElement<
     {
       onMouseEnter?: MouseEventHandler<Element> | undefined;
       onClick: MouseEventHandler;
@@ -295,6 +360,16 @@ declare module "next/link" {
     HTMLElement
   >;
 
+  declare function Link(
+    props: PropsWithChildren<LinkProps<Route>>
+  ): LinkReactElement;
+  declare function Link(
+    props: PropsWithChildren<LinkProps<Route["pathname"]>>
+  ): LinkReactElement;
+  declare function Link(
+    props: PropsWithChildren<LinkProps<Query>>
+  ): LinkReactElement;
+
   export default Link;
 }
 
@@ -307,18 +382,19 @@ declare module "next/router" {
 
   type NextTransitionOptions = NonNullable<Parameters<Router["push"]>[2]>;
 
-  interface TransitionOptions extends Omit<NextTransitionOptions, 'locale'> {
+  interface TransitionOptions extends Omit<NextTransitionOptions, "locale"> {
     locale?: false;
   };
 
-  type RouteOrQuery = 
-    | Route
-    | { query: { [key: string]: string | string[] | undefined } };
-
   export interface NextRouter<P extends Route["pathname"] = Route["pathname"]>
     extends Omit<
       Router,
-      "push" | "replace" | "locale" | "locales" | "defaultLocale" | "domainLocales"
+      | "push"
+      | "replace"
+      | "locale"
+      | "locales"
+      | "defaultLocale"
+      | "domainLocales"
     > {
     defaultLocale?: undefined;
     domainLocales?: undefined;
@@ -326,13 +402,33 @@ declare module "next/router" {
     locales?: undefined;
     pathname: P;
     push(
-      url: RouteOrQuery,
+      url: Route,
+      as?: string,
+      options?: TransitionOptions
+    ): Promise<boolean>;
+    push(
+      url: Route["pathname"],
+      as?: string,
+      options?: TransitionOptions
+    ): Promise<boolean>;
+    push(
+      url: { query: { [key: string]: string | string[] | undefined } },
       as?: string,
       options?: TransitionOptions
     ): Promise<boolean>;
     query: RoutedQuery<P>;
     replace(
-      url: RouteOrQuery,
+      url: Route,
+      as?: string,
+      options?: TransitionOptions
+    ): Promise<boolean>;
+    replace(
+      url: Route["pathname"],
+      as?: string,
+      options?: TransitionOptions
+    ): Promise<boolean>;
+    replace(
+      url: { query: { [key: string]: string | string[] | undefined } },
       as?: string,
       options?: TransitionOptions
     ): Promise<boolean>;
@@ -359,7 +455,9 @@ declare module "nextjs-routes" {
   export type Route =
     | { pathname: "/404"; query?: Query | undefined };
 
-  type Query<Params = {}> = Params & { [key: string]: string | string[] | undefined };
+  type Query<Params = {}> = Params & {
+    [key: string]: string | string[] | undefined;
+  };
 
   type QueryForPathname = {
     [K in Route as K["pathname"]]: Exclude<K["query"], undefined>;
@@ -368,11 +466,11 @@ declare module "nextjs-routes" {
   export type RoutedQuery<P extends Route["pathname"]> = QueryForPathname[P];
 
   export type Locale = undefined;
-  
+
   /**
    * A typesafe utility function for generating paths in your application.
    *
-   * route({ pathname: '/foos/[foo]', query: { foo: 'bar' }}) will produce '/foos/bar'.
+   * route({ pathname: "/foos/[foo]", query: { foo: "bar" }}) will produce "/foos/bar".
    */
   export declare function route(r: Route): string;
 }
@@ -384,16 +482,15 @@ declare module "next/link" {
   import type { PropsWithChildren, MouseEventHandler } from "react";
   export * from "next/dist/client/link";
 
-  type RouteOrQuery = Route | { query?: { [key: string]: string | string[] | undefined } };
+  type Query = { query?: { [key: string]: string | string[] | undefined } };
 
-  export interface LinkProps extends Omit<NextLinkProps, "href" | "locale"> {
-    href: RouteOrQuery;
+  export interface LinkProps<Href extends Route | Query = Route | Query>
+    extends Omit<NextLinkProps, "href" | "locale"> {
+    href: Href;
     locale?: false;
   }
 
-  declare function Link(
-    props: PropsWithChildren<LinkProps>
-  ): DetailedReactHTMLElement<
+  type LinkReactElement = DetailedReactHTMLElement<
     {
       onMouseEnter?: MouseEventHandler<Element> | undefined;
       onClick: MouseEventHandler;
@@ -403,6 +500,16 @@ declare module "next/link" {
     HTMLElement
   >;
 
+  declare function Link(
+    props: PropsWithChildren<LinkProps<Route>>
+  ): LinkReactElement;
+  declare function Link(
+    props: PropsWithChildren<LinkProps<Route["pathname"]>>
+  ): LinkReactElement;
+  declare function Link(
+    props: PropsWithChildren<LinkProps<Query>>
+  ): LinkReactElement;
+
   export default Link;
 }
 
@@ -415,18 +522,19 @@ declare module "next/router" {
 
   type NextTransitionOptions = NonNullable<Parameters<Router["push"]>[2]>;
 
-  interface TransitionOptions extends Omit<NextTransitionOptions, 'locale'> {
+  interface TransitionOptions extends Omit<NextTransitionOptions, "locale"> {
     locale?: false;
   };
 
-  type RouteOrQuery = 
-    | Route
-    | { query: { [key: string]: string | string[] | undefined } };
-
   export interface NextRouter<P extends Route["pathname"] = Route["pathname"]>
     extends Omit<
       Router,
-      "push" | "replace" | "locale" | "locales" | "defaultLocale" | "domainLocales"
+      | "push"
+      | "replace"
+      | "locale"
+      | "locales"
+      | "defaultLocale"
+      | "domainLocales"
     > {
     defaultLocale?: undefined;
     domainLocales?: undefined;
@@ -434,13 +542,33 @@ declare module "next/router" {
     locales?: undefined;
     pathname: P;
     push(
-      url: RouteOrQuery,
+      url: Route,
+      as?: string,
+      options?: TransitionOptions
+    ): Promise<boolean>;
+    push(
+      url: Route["pathname"],
+      as?: string,
+      options?: TransitionOptions
+    ): Promise<boolean>;
+    push(
+      url: { query: { [key: string]: string | string[] | undefined } },
       as?: string,
       options?: TransitionOptions
     ): Promise<boolean>;
     query: RoutedQuery<P>;
     replace(
-      url: RouteOrQuery,
+      url: Route,
+      as?: string,
+      options?: TransitionOptions
+    ): Promise<boolean>;
+    replace(
+      url: Route["pathname"],
+      as?: string,
+      options?: TransitionOptions
+    ): Promise<boolean>;
+    replace(
+      url: { query: { [key: string]: string | string[] | undefined } },
       as?: string,
       options?: TransitionOptions
     ): Promise<boolean>;
@@ -467,7 +595,9 @@ declare module "nextjs-routes" {
   export type Route =
     never;
 
-  type Query<Params = {}> = Params & { [key: string]: string | string[] | undefined };
+  type Query<Params = {}> = Params & {
+    [key: string]: string | string[] | undefined;
+  };
 
   type QueryForPathname = {
     [K in Route as K["pathname"]]: Exclude<K["query"], undefined>;
@@ -476,11 +606,11 @@ declare module "nextjs-routes" {
   export type RoutedQuery<P extends Route["pathname"]> = QueryForPathname[P];
 
   export type Locale = undefined;
-  
+
   /**
    * A typesafe utility function for generating paths in your application.
    *
-   * route({ pathname: '/foos/[foo]', query: { foo: 'bar' }}) will produce '/foos/bar'.
+   * route({ pathname: "/foos/[foo]", query: { foo: "bar" }}) will produce "/foos/bar".
    */
   export declare function route(r: Route): string;
 }
@@ -492,16 +622,15 @@ declare module "next/link" {
   import type { PropsWithChildren, MouseEventHandler } from "react";
   export * from "next/dist/client/link";
 
-  type RouteOrQuery = Route | { query?: { [key: string]: string | string[] | undefined } };
+  type Query = { query?: { [key: string]: string | string[] | undefined } };
 
-  export interface LinkProps extends Omit<NextLinkProps, "href" | "locale"> {
-    href: RouteOrQuery;
+  export interface LinkProps<Href extends Route | Query = Route | Query>
+    extends Omit<NextLinkProps, "href" | "locale"> {
+    href: Href;
     locale?: false;
   }
 
-  declare function Link(
-    props: PropsWithChildren<LinkProps>
-  ): DetailedReactHTMLElement<
+  type LinkReactElement = DetailedReactHTMLElement<
     {
       onMouseEnter?: MouseEventHandler<Element> | undefined;
       onClick: MouseEventHandler;
@@ -511,6 +640,16 @@ declare module "next/link" {
     HTMLElement
   >;
 
+  declare function Link(
+    props: PropsWithChildren<LinkProps<Route>>
+  ): LinkReactElement;
+  declare function Link(
+    props: PropsWithChildren<LinkProps<Route["pathname"]>>
+  ): LinkReactElement;
+  declare function Link(
+    props: PropsWithChildren<LinkProps<Query>>
+  ): LinkReactElement;
+
   export default Link;
 }
 
@@ -523,18 +662,19 @@ declare module "next/router" {
 
   type NextTransitionOptions = NonNullable<Parameters<Router["push"]>[2]>;
 
-  interface TransitionOptions extends Omit<NextTransitionOptions, 'locale'> {
+  interface TransitionOptions extends Omit<NextTransitionOptions, "locale"> {
     locale?: false;
   };
 
-  type RouteOrQuery = 
-    | Route
-    | { query: { [key: string]: string | string[] | undefined } };
-
   export interface NextRouter<P extends Route["pathname"] = Route["pathname"]>
     extends Omit<
       Router,
-      "push" | "replace" | "locale" | "locales" | "defaultLocale" | "domainLocales"
+      | "push"
+      | "replace"
+      | "locale"
+      | "locales"
+      | "defaultLocale"
+      | "domainLocales"
     > {
     defaultLocale?: undefined;
     domainLocales?: undefined;
@@ -542,13 +682,33 @@ declare module "next/router" {
     locales?: undefined;
     pathname: P;
     push(
-      url: RouteOrQuery,
+      url: Route,
+      as?: string,
+      options?: TransitionOptions
+    ): Promise<boolean>;
+    push(
+      url: Route["pathname"],
+      as?: string,
+      options?: TransitionOptions
+    ): Promise<boolean>;
+    push(
+      url: { query: { [key: string]: string | string[] | undefined } },
       as?: string,
       options?: TransitionOptions
     ): Promise<boolean>;
     query: RoutedQuery<P>;
     replace(
-      url: RouteOrQuery,
+      url: Route,
+      as?: string,
+      options?: TransitionOptions
+    ): Promise<boolean>;
+    replace(
+      url: Route["pathname"],
+      as?: string,
+      options?: TransitionOptions
+    ): Promise<boolean>;
+    replace(
+      url: { query: { [key: string]: string | string[] | undefined } },
       as?: string,
       options?: TransitionOptions
     ): Promise<boolean>;
@@ -592,7 +752,9 @@ declare module "nextjs-routes" {
     | { pathname: "/settings/foo"; query?: Query | undefined }
     | { pathname: "/settings"; query?: Query | undefined };
 
-  type Query<Params = {}> = Params & { [key: string]: string | string[] | undefined };
+  type Query<Params = {}> = Params & {
+    [key: string]: string | string[] | undefined;
+  };
 
   type QueryForPathname = {
     [K in Route as K["pathname"]]: Exclude<K["query"], undefined>;
@@ -601,11 +763,11 @@ declare module "nextjs-routes" {
   export type RoutedQuery<P extends Route["pathname"]> = QueryForPathname[P];
 
   export type Locale = undefined;
-  
+
   /**
    * A typesafe utility function for generating paths in your application.
    *
-   * route({ pathname: '/foos/[foo]', query: { foo: 'bar' }}) will produce '/foos/bar'.
+   * route({ pathname: "/foos/[foo]", query: { foo: "bar" }}) will produce "/foos/bar".
    */
   export declare function route(r: Route): string;
 }
@@ -617,16 +779,15 @@ declare module "next/link" {
   import type { PropsWithChildren, MouseEventHandler } from "react";
   export * from "next/dist/client/link";
 
-  type RouteOrQuery = Route | { query?: { [key: string]: string | string[] | undefined } };
+  type Query = { query?: { [key: string]: string | string[] | undefined } };
 
-  export interface LinkProps extends Omit<NextLinkProps, "href" | "locale"> {
-    href: RouteOrQuery;
+  export interface LinkProps<Href extends Route | Query = Route | Query>
+    extends Omit<NextLinkProps, "href" | "locale"> {
+    href: Href;
     locale?: false;
   }
 
-  declare function Link(
-    props: PropsWithChildren<LinkProps>
-  ): DetailedReactHTMLElement<
+  type LinkReactElement = DetailedReactHTMLElement<
     {
       onMouseEnter?: MouseEventHandler<Element> | undefined;
       onClick: MouseEventHandler;
@@ -636,6 +797,16 @@ declare module "next/link" {
     HTMLElement
   >;
 
+  declare function Link(
+    props: PropsWithChildren<LinkProps<Route>>
+  ): LinkReactElement;
+  declare function Link(
+    props: PropsWithChildren<LinkProps<Route["pathname"]>>
+  ): LinkReactElement;
+  declare function Link(
+    props: PropsWithChildren<LinkProps<Query>>
+  ): LinkReactElement;
+
   export default Link;
 }
 
@@ -648,18 +819,19 @@ declare module "next/router" {
 
   type NextTransitionOptions = NonNullable<Parameters<Router["push"]>[2]>;
 
-  interface TransitionOptions extends Omit<NextTransitionOptions, 'locale'> {
+  interface TransitionOptions extends Omit<NextTransitionOptions, "locale"> {
     locale?: false;
   };
 
-  type RouteOrQuery = 
-    | Route
-    | { query: { [key: string]: string | string[] | undefined } };
-
   export interface NextRouter<P extends Route["pathname"] = Route["pathname"]>
     extends Omit<
       Router,
-      "push" | "replace" | "locale" | "locales" | "defaultLocale" | "domainLocales"
+      | "push"
+      | "replace"
+      | "locale"
+      | "locales"
+      | "defaultLocale"
+      | "domainLocales"
     > {
     defaultLocale?: undefined;
     domainLocales?: undefined;
@@ -667,13 +839,33 @@ declare module "next/router" {
     locales?: undefined;
     pathname: P;
     push(
-      url: RouteOrQuery,
+      url: Route,
+      as?: string,
+      options?: TransitionOptions
+    ): Promise<boolean>;
+    push(
+      url: Route["pathname"],
+      as?: string,
+      options?: TransitionOptions
+    ): Promise<boolean>;
+    push(
+      url: { query: { [key: string]: string | string[] | undefined } },
       as?: string,
       options?: TransitionOptions
     ): Promise<boolean>;
     query: RoutedQuery<P>;
     replace(
-      url: RouteOrQuery,
+      url: Route,
+      as?: string,
+      options?: TransitionOptions
+    ): Promise<boolean>;
+    replace(
+      url: Route["pathname"],
+      as?: string,
+      options?: TransitionOptions
+    ): Promise<boolean>;
+    replace(
+      url: { query: { [key: string]: string | string[] | undefined } },
       as?: string,
       options?: TransitionOptions
     ): Promise<boolean>;