August 11, 2023

An experiment to make next.js routes usage safer

🔬 next-type-routes

mit licence npm version bundlephobia

An experiment to make next.js routes usage safer.
Heavily inspired by my work on @swan-io/chicane

⚠️ Don't use this in production (yet)!

Installation

$ yarn add next-type-routes
# --- or ---
$ npm install --save next-type-routes

Quickstart

First, you have to generate the typed routes functions. For that, I recommend using npm scripts:

"scripts": {
  "type-routes": "type-routes src/routes.ts",
  "dev": "yarn type-routes && next dev",
  "build": "yarn type-routes && next build",

When ran, this command parse your pages tree and generates a TS file (src/routes.ts) which looks like this:

import { createTypedFns } from "next-type-routes";

export const {
  createURL,
  getApiRequestParams,
  getServerSideParams,
  useRouterWithSSR,
  useRouterWithNoSSR,
} = createTypedFns([
  // Here will be all your project routes:
  "/",
  "/api/auth/login",
  "/api/projects/[projectId]",
  "/projects",
  "/projects/[projectId]",
  "/users",
  "/users/[userId]",
  "/users/[userId]/favorites/[[...rest]]",
  "/users/[userId]/repositories",
  "/users/[userId]/repositories/[repositoryId]",
]);

API

createURL

import Link from "next/link";
import { createURL } from "path/to/routes";

export default function ExamplePage() {
  return (
    <>
      

Users

{/* URL params are type safe! */} zoontek ); }

useRouterWithSSR

import { useRouterWithSSR } from "path/to/routes";

export default function ExamplePage() {
  const { params } = useRouterWithSSR("/users/[userId]"); // we can use useRouterWithSSR since getServerSideProps is used
  const { userId } = params.route; // userId type is string

  return ;
}

useRouterWithNoSSR

import { useRouterWithNoSSR } from "path/to/routes";

export default function ExamplePage() {
  const { params } = useRouterWithNoSSR("/users/[userId]"); // we have to use useRouterWithNoSSR since getServerSideProps is not used
  const { userId } = params.route; // userId type is string | undefined

  if (userId == null) {
    return Loading…;
  }

  return ;
}

getApiRequestParams

import type { NextApiRequest, NextApiResponse } from "next";
import { getApiRequestParams } from "path/to/routes";

export default function handler(req: NextApiRequest, res: NextApiResponse) {
  // we can access params in a safe way on API routes
  const params = getApiRequestParams("/api/projects/[projectId]", req);

  res.status(200).json({ handler: "project", params });
}

getServerSideParams

import { GetServerSideProps } from "next";
import Link from "next/link";
import { getServerSideParams } from "path/to/routes";

export const getServerSideProps: GetServerSideProps = async (context) => {
  const params = getServerSideParams("/users/[userId]", context);

  // we can access params in a safe way on the server
  console.log(params.route.userId);

  return { props: {} };
};

Error handling

What happen when I, let's say, use useRouterWithSSR("/users/[userId]") in page with /project/[projectId] path?
Well, it will throw an error 💥. That's why I highly recommend to create a 500.tsx page and wrap your app in an Error Boundary.