Search/Route Params- Pages
Versions
While the apis for app and pages are very similar, there are some important differences.
Make sure you follow the docs for your use case
This page: pages version
View the app version here
Hooks
next-typesafe-url/pages exports a useRouteParams and useSearchParams hook that will return the route params / search params for the current route. They take one argument, the zod schema for either route params or search params from the Route object.
import { useSearchParams, useRouteParams } from "next-typesafe-url/pages";
export default function Component() {
const routeParams = useRouteParams(Route.routeParams);
const searchParams = useSearchParams(Route.searchParams);
const { data, isLoading, isError, error } = searchParams;
if (isLoading) {
return <div>loading...</div>;
} else if (isError) {
return <div>Invalid search params {error.message}</div>;
} else {
return <div>{data.userInfo.name}</div>;
}
}
Errors
isLoading is the loading state of the internal Next router, and isError is a boolean that is true if the params do not match the schema. If isError is true, then error will be a ZodError object you can use to get more information about the error. (also check out zod-validation-error to get a nice error message)
If isLoading is false and isError is false, then data will always be valid and match the schema.
IMPORT WARNING
next-typesafe-url/app ALSO exports a useRouteParams and useSearchParams hook, but these are NOT the same as the hooks exported from next-typesafe-url/pages.
MAKE SURE YOU IMPORT FROM THE RIGHT PATH
Recommended Usage
As I mentioned earlier, it is important that only the RouteType, not the Route is exported to not break hot reloading.
This means you should only call useSearchParams and useRouteParams in the top level component of each route, and pass the data down to child components through props or context.
Feel free to use your state management library of choice to pass the data down to child components.
getStatic/SeverSide Props
next-typesafe-url provides full support for validating route params and search params in getStaticProps and getServerSideProps.
The parseServerSideParams function is used to parse the route params and search params on the server. They take the same schema from your Route object as the useRouteParams and useSearchParams hooks, as well as parts of context object from getStaticProps / getServerSideProps.
Route Params
To parse route params, pass the params field from the gssp context object, as well as the route params schema from your Route object. Note this field is possibly undefined, so you should check for that before passing it to parseServerSideParams.
Search Params
To parse search params, pass the query field from the gssp context object, as well as the search params schema from your Route object.
Errors
Like the hooks, parseServerSideParams have an isError flag, and if it is true, then error will be a ZodError you can use to get more information about the error.
This is an example of how to use next-typesafe-url with getServerSideProps, but the same pattern can be used with getStaticProps.
In this example I simply pass all of the params as props, but you can use the fully typed and validated data you get back from parseServerSideParams however you wish.
// pages/product/[productID].tsx
import type {
InferGetServerSidePropsType,
NextPage,
GetServerSideProps,
} from "next";
import { z } from "zod";
import { type AppRouter } from "next-typesafe-url";
import { parseServerSideParams } from "next-typesafe-url/pages";
const Route = {
routeParams: z.object({
productID: z.number(),
}),
searchParams: z.object({
location: z.enum(["us", "eu"]).optional(),
userInfo: z.object({
name: z.string(),
age: z.number(),
}),
}),
} satisfies DynamicRoute;
export type RouteType = typeof Route;
type ServerSideProps = AppRouter["/product/[productID]"]["searchParams"] &
AppRouter["/product/[productID]"]["routeParams"];
export const getServerSideProps: GetServerSideProps<ServerSideProps> = async (
context,
) => {
const routeParams = parseServerSideParams({
params: context.params ?? {},
validator: Route.routeParams,
});
const searchParams = parseServerSideParams({
params: context.query,
validator: Route.searchParams,
});
if (routeParams.isError || searchParams.isError) {
console.log(routeParams.error?.message, searchParams.error?.message);
throw new Error("Invalid route or search params");
} else {
return {
props: {
routeParams: routeParams.data,
searchParams: searchParams.data,
},
};
}
};
type PageProps = InferGetServerSidePropsType<typeof getServerSideProps>;
const Page: NextPage<PageProps> = ({ searchParams, routeParams }) => {
return (
<>
<div>productID: {routeParams.productID}</div>
<div>
user: {`${searchParams.userInfo.name} - ${searchParams.userInfo.age}`}
</div>
<div>location: {searchParams.location}</div>
</>
);
};
export default Page;