Dynamic Route Typing
Unanswered
Luke posted this in #help-forum
LukeOP
Hey all, quick preface: I don't necessarily think this belongs in the help-forum, I would have preferred to put this in #discussions, but I don't have write permissions at the moment.
Here's my question/discussion:
In Next.js, we have dynamic routes, the basic example would be:
But, let's say a
But, there is an odd caveat, which is that all params come in as strings (unless I'm mistaken), no matter what.
If I type
This makes sense, as routes are inherently strings, and I don't expect Next to handle the conversion for me, or even worse, make assumptions about the type and perform an automatic conversion.
But, how does this have implications on best practices? If I put
Am I mistaken in assuming that this is problematic? Should a type exist that allows you to define what the params are but not their types to avoid confusion for users?
Here's my question/discussion:
In Next.js, we have dynamic routes, the basic example would be:
// /users/[userId]/page.tsx
export default function UserPage({
params,
}: {
params: { userId: string };
}) {}But, let's say a
userId is/should be a number, and not a string. Then I would go ahead and change the params type to:params: { userId: number }But, there is an odd caveat, which is that all params come in as strings (unless I'm mistaken), no matter what.
If I type
params.userId to be a number, it's actually a string.This makes sense, as routes are inherently strings, and I don't expect Next to handle the conversion for me, or even worse, make assumptions about the type and perform an automatic conversion.
But, how does this have implications on best practices? If I put
number as the type, the code all works, and I get no linting errors.Am I mistaken in assuming that this is problematic? Should a type exist that allows you to define what the params are but not their types to avoid confusion for users?
34 Replies
@Luke Hey all, quick preface: I don't necessarily think this belongs in the help-forum, I would have preferred to put this in <#752647196419031042>, but I don't have write permissions at the moment.
Here's my question/discussion:
In Next.js, we have dynamic routes, the basic example would be:
ts
// /users/[userId]/page.tsx
export default function UserPage({
params,
}: {
params: { userId: string };
}) {}
But, let's say a `userId` is/should be a number, and not a string. Then I would go ahead and change the params type to:
ts
params: { userId: number }
But, there is an odd caveat, which is that all params come in as strings (unless I'm mistaken), no matter what.
If I type `params.userId` to be a number, it's actually a string.
This makes sense, as routes are inherently strings, and I don't expect Next to handle the conversion for me, or even worse, make assumptions about the type and perform an automatic conversion.
But, how does this have implications on best practices? If I put `number` as the type, the code all works, and I get no linting errors.
Am I mistaken in assuming that this is problematic? Should a type exist that allows you to define what the params _are_ but not their types to avoid confusion for users?
you can make simple if conditions to make it typesafe. It could look like this:
if(isNaN(parseInt(params.id))) {
console.log("is not a number");
return;
}
// params.id should now be typesave a numberLukeOP
Yeah, I guess I'm more concerned with the foot gun of typing a route param as something other than a string
I made a basic type to make my life less stressful for this, and to prevent any accidents across the app for my whole team
declare module "next" {
type DynamicRouteParams<T extends string> = {
params: {
[K in T]: string;
};
};
}Basic usage:
export default
async function UserPage({
params,
}: DynamicRouteParams<"userId">) {I am personally of the opinion that a type like this should be exposed by default from next to prevent any misuse and incorrect typing.
Web urls are all string based.
When you type your localhost/users/2 in a browser tab you enter it as string.
Next is only giving you the string value of your dynamic route!
If but you can override the type by using Number function
When you type your localhost/users/2 in a browser tab you enter it as string.
Next is only giving you the string value of your dynamic route!
If but you can override the type by using Number function
@ItetsuLaTable Web urls are all string based.
When you type your localhost/users/2 in a browser tab you enter it as string.
Next is only giving you the string value of your dynamic route!
If but you can override the type by using Number function
LukeOP
Definitely get that, my concern really lies with the fact that you can just freely type the incoming params as whatever you'd like with no errors on linting, etc
I got your point to have a number instead of string but that the only case
You cannot type it as custom type or array
LukeOP
@ItetsuLaTable I'd argue it's not, you could type a route param as a union like
While in reality, that quote param could be anything
params: { type: "test" | "hello" }While in reality, that quote param could be anything
Well for the DX that a cool stuff to have for sure!
LukeOP
Yeah, i don't think it's a huge deal, I'm definitely making more of it than it really is
But would be really nice if Next provided some typing / additional docs on that
Yes but if you type it as union whats going on if you enter toto instead of test or hello? Is that a 404 or 500 ?
LukeOP
now I'm curious
let me try
Because ts wont be happy because it does not match
LukeOP
Yeah, this is exactly what I'm concerned with:
// test/[testParam]/page.tsx
export default async function TestPage({
params,
}: {
params: { testParam: "foo" | "bar" };
}) {
console.log(params);
return <h1>{JSON.stringify(params)}</h1>;
}Here is a very basic example
And in a perfect world, you'd think that testParams is either foo or bar, and other things would cause errors, etc
But that's not the case, testParam works no matter what I set it to on the route
It's prime for runtime errors, and the next docs mention nothing about that
Even just a simple "All params come in as a
string, there is no enforcement of the types that you may set" would be such a good thing for them to noteI guess its due to typing isnot supported here
LukeOP
Yeah, I mean I totally get why they all come in as strings
I just don't think that Next should then allow you to type them as whatever you'd hypothetically like
I agree! I dont think Next should allow us to do it or we’ll found many issues
LukeOP
❤️ I'm glad I'm not alone
I think it's especially a problem on larger teams
If I have 12 people on my team, I can't expect all of them to know that params come in as indiscriminate strings
I just extended the next module (which I usually discourage) and added a "DynamicRouteParams" type that allows you to say the name of the route params, but no types, it will just set them to string (to match nextjs behavior)
like this:
new type
Basic usage:
new type
declare module "next" {
type DynamicRouteParams<T extends string> = {
params: {
[K in T]: string;
};
};
}Basic usage:
export default
async function UserPage({
params,
}: DynamicRouteParams<"userId">) {@Luke typescript
declare module "next" {
type DynamicRouteParams<T extends string> = {
params: {
[K in T]: string;
};
};
}
Basic usage:
ts
export default
async function UserPage({
params,
}: DynamicRouteParams<"userId">) {
I am personally of the opinion that a type like this should be exposed by default from next to prevent any misuse and incorrect typing.
Except… in catchall routes, it’s not a string, but rather an array of strings or undefined. So that DynamicRouteParams type is impossible to implement.
You just need to remember that it is always string and not numbers or union of literal strings or whatever you can come up with. So you need to specify it as string and handle the conversion to the format you like manually.
Also, pretty sure that if you put number or string union or anything that is wrong in there, the build will fail because next does check the function signature there.