Statically Typed Links not working when passing href
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 142k
- Forks
- 32.4k
- Avg merge
- 2d 14h
- Merged PRs (30d)
- 351
Description
Verify canary release
- I verified that the issue exists in the latest Next.js canary release
Provide environment information
Operating System:
Platform: darwin
Arch: x64
Version: Darwin Kernel Version 21.6.0: Mon Dec 19 20:44:01 PST 2022; root:xnu-8020.240.18~2/RELEASE_X86_64
Binaries:
Node: 18.15.0
npm: 9.5.0
Yarn: N/A
pnpm: N/A
Relevant packages:
next: 13.2.5-canary.21
eslint-config-next: 13.2.4
react: 18.2.0
react-dom: 18.2.0
Which area(s) of Next.js are affected? (leave empty if unsure)
App directory (appDir: true), TypeScript
Link to the code that reproduces this issue
To Reproduce
I've been trying server components with the new "app" directory with the typedRoutes: true config set too true.
It generates the following: routes definitions in the Next.js folder:
type StaticRoutes =
| `/`
| `/pokedex`
| `/pokedex/create`
| `/todo`
| `/weather`
| `/404.page`
| `/404.test`
| `/500.test`
| `/500.page`
type DynamicRoutes<T extends string = string> =
| `/pokedex/${SafeSlug<T>}`
| `/pokedex/${SafeSlug<T>}/evolutions`
| `/pokedex/${SafeSlug<T>}/edit`
| `/weather/${SafeSlug<T>}`
| `/api/${OptionalCatchAllSlug<T>}`
type RouteImpl<T> =
| StaticRoutes
| `${StaticRoutes}${Suffix}`
| (T extends `${DynamicRoutes<infer _>}${Suffix}` ? T : never)
I get a TypeScript error in a component called <Pager> it creates a two button
pagination component:
import Link from 'next/link';
import { Page } from '../../types';
import { Route } from 'next';
type Props<T extends string> = {
page: Page<unknown>;
hrefForPage(page: string): Route<T> | URL;
};
export function Pager<T extends string>(props: Props<T>) {
const { page } = props;
if (page.first && page.last) {
return null;
}
const prevPage = `${page.number - 1}`;
const nextPage = `${page.number + 1}`;
return (
<div>
<Link href={props.hrefForPage(prevPage)}>
<button disabled={page.first}>prev</button>
</Link>
<Link href={props.hrefForPage(nextPage)}>
<button disabled={page.last}>next</button>
</Link>
</div>
);
}
The idea behind the hrefForPage is that it is a callback function, which should return the proper url for
the next and previous Link, given the page number as a string.
This differs from the beta documentation example, as that example passes the href directly, with no function in between.
Note: that I also have components which do pass the href like in the documentation, and this works like a charm.
TypeScript does not however agree that the hrefForPage returns the correct type:
> tsc --version && tsc --noEmit
Version 5.0.2
src/components/Pager/Pager.tsx:22:13 - error TS2322: Type 'URL | Route<T>' is not assignable to type 'UrlObject | RouteImpl<URL>'.
Type 'T extends `/pokedex/${SafeSlug<infer _ extends string>}` | `/pokedex/${SafeSlug<infer _ extends string>}/edit` | `/pokedex/${SafeSlug<infer _ extends string>}/evolutions` | `/weather/${SafeSlug<infer _ extends string>}` | ... 10 more ... | `/api/${OptionalCatchAllSlug<...>}#${string}` ? T : never' is not assignable to type 'UrlObject | RouteImpl<URL>'.
Type '(`/pokedex/${string}` | `/pokedex/${string}/edit` | `/pokedex/${string}/evolutions` | `/weather/${string}` | `/api/${string}` | `/pokedex/${string}?${string}` | `/pokedex/${string}#${string}` | `/pokedex/${string}/edit?${string}` | `/pokedex/${string}/edit#${string}` | `/pokedex/${string}/evolutions?${string}` | `/p...' is not assignable to type 'UrlObject | RouteImpl<URL>'.
Type '`/pokedex/${string}` & T' is not assignable to type 'UrlObject | RouteImpl<URL>'.
Type '`/pokedex/${string}` & T' is not assignable to type 'UrlObject'.
Type 'T extends `/pokedex/${SafeSlug<infer _ extends string>}` | `/pokedex/${SafeSlug<infer _ extends string>}/edit` | `/pokedex/${SafeSlug<infer _ extends string>}/evolutions` | `/weather/${SafeSlug<infer _ extends string>}` | ... 10 more ... | `/api/${OptionalCatchAllSlug<...>}#${string}` ? T : never' is not assignable to type 'UrlObject'.
Type '(`/pokedex/${string}` | `/pokedex/${string}/edit` | `/pokedex/${string}/evolutions` | `/weather/${string}` | `/api/${string}` | `/pokedex/${string}?${string}` | `/pokedex/${string}#${string}` | `/pokedex/${string}/edit?${string}` | `/pokedex/${string}/edit#${string}` | `/pokedex/${string}/evolutions?${string}` | `/p...' is not assignable to type 'UrlObject'.
Type '`/pokedex/${string}` & T' is not assignable to type 'UrlObject'.
Types of property 'search' are incompatible.
Type '{ (regexp: string | RegExp): number; (searcher: { [Symbol.search](string: string): number; }): number; }' is not assignable to type 'string'.
22 <Link href={props.hrefForPage(prevPage)}>
~~~~
.next/types/link.d.ts:78:5
78 href: __next_route_internal_types__.RouteImpl<T> | UrlObject
~~~~
The expected type comes from property 'href' which is declared here on type 'IntrinsicAttributes & LinkRestProps & { href: UrlObject | RouteImpl<URL>; }'
src/components/Pager/Pager.tsx:25:13 - error TS2322: Type 'URL | Route<T>' is not assignable to type 'UrlObject | RouteImpl<URL>'.
25 <Link href={props.hrefForPage(nextPage)}>
~~~~
.next/types/link.d.ts:78:5
78 href: __next_route_internal_types__.RouteImpl<T> | UrlObject
~~~~
The expected type comes from property 'href' which is declared here on type 'IntrinsicAttributes & LinkRestProps & { href: UrlObject | RouteImpl<URL>; }'
Found 2 errors in the same file, starting at: src/components/Pager/Pager.tsx:22
Describe the Bug
TypeScript gives an error whenever a callback function returns Route<T> | URL when passing the result of that callback function to the href prop of the Link component.
Expected Behavior
I expect that TypeScript thinks that the result of a function that returns Route<T> | URL should be valid.
Which browser are you using? (if relevant)
No response
How are you deploying your application? (if relevant)
No response
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Reproduce the report from the linked CodeSandbox and run tsc --noEmit with TypeScript 5.0.2. Inspect the generated .next/types/link.d.ts, especially the Link href declaration, alongside the Pager callback type. Done means a callback returning Route | URL can be passed to Link without the reported type errors.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- nextjs, react, typescript
- Domain
- frontend, web-dev
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 38/100