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

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;

Finally, explore the full API reference

Next: API Reference