Search code examples
reactjsnext.jsapi-design

prettier urls with nextjs routes


I'm building out a new marketing site for my company using next.js, and I'm running into an issues with URLS. Essentially, I've built a custom API route to access data from our internal database, using Prisma:

getAllDealers.ts

import Cors from 'cors';
import { prisma } from 'lib/prisma';
import { NextApiResponse, NextApiRequest, NextApiHandler } from 'next';

const cors = Cors({
  methods: ['GET', 'HEAD'],
});

function runMiddleware(req: NextApiRequest, res: NextApiResponse, fn: any) {
  return new Promise((resolve, reject) => {
    fn(req, res, (result: any) => {
      if (result instanceof Error) {
        return reject(result);
      }

      return resolve(result);
    });
  });
}

const getDealers: NextApiHandler = async (req: NextApiRequest, res: NextApiResponse) => {
  const { method } = req;

  await runMiddleware(req, res, cors);
  const dealers = await prisma.crm_dealers.findMany({
    where: {
      active: {
        not: 0,
      },
    },
  });

  switch (method) {
    case 'GET':
      res.status(200).send({ dealers, method: method });
      break;
    case 'PUT':
      res.status(500).json({ message: 'sorry, we only accept GET requests', method: method });
      break;
    default:
      res.setHeader('Allow', ['GET']);
      res.status(405).end(`Method ${method} Not Allowed`);
  }
};

export default getDealers;

And I've built a route to access individual dealers:

getSingleDealer.ts

import Cors from 'cors';
import { prisma } from 'lib/prisma';
import { NextApiResponse, NextApiRequest, NextApiHandler } from 'next';

const cors = Cors({
  methods: ['GET', 'HEAD'],
});

function runMiddleware(req: NextApiRequest, res: NextApiResponse, fn: any) {
  return new Promise((resolve, reject) => {
    fn(req, res, (result: any) => {
      if (result instanceof Error) {
        return reject(result);
      }

      return resolve(result);
    });
  });
}

const getDealerById: NextApiHandler = async (req: NextApiRequest, res: NextApiResponse) => {
  await runMiddleware(req, res, cors);
  const dealer = await prisma.crm_dealers.findUnique({
    where: {
      id: Number(req.query.id),
    },
  });

  res.status(200).send({ dealer, method: req.method });
};

export default getDealerById;

I can use my getSingleDealer function in getServerSideProps like so:

export const getServerSideProps = async ({ params }: Params) => {
  const { uid } = params;

  const { dealer } = await getSingleDealer('api/dealer', uid);

  return {
    props: { dealer },
  };
};

And this works just fine. What I need to do though is prettify my URLS. Right now, the way to access a singular dealer's page is dealers/1 with 1 being whatever the ID of the dealer is. I want to have that URL be a string, like dealers/sacramento-ca (that location is also served up in the API) while still accessing the API on the basis of the id so it's searching for an integer, rather than a string. Is that possible within next?


Solution

  • You'd handle the routing in your client with getServerSideProps similarly to what you are doing now. To do so, you need to configure your dynamic routing file or folder name to match your desired format.

    Example folder structures are:

    pages > dealers > [dealer].tsx = /dealers/sacramento-ca

    pages > dealers > [location] > index.tsx = /dealers/sacramento-ca

    export const getServerSideProps = async ({ params }: Params) => {
      const { uid } = params;
      const { dealer } = await getSingleDealer('api/dealer', uid);
    
      if (!dealer ) {
        return { notFound: true }
      }
    
      return {
        props: { 
         ...dealer,
         location: 'sacramento-ca', // object key must match your dynamic [folder or file's name]
        },
      };
    };
    

    All dynamic URL parts must be included as a key in the return.

    pages > dealers > [state] > index.tsx [city].tsx = /dealers/ca/sacramento

    return {
      props: {
       ...dealer,
       city: 'sacramento',
       state: 'ca',
      },
    };
    

    Here is a article detailing what you will need to do. It's important to note that sometimes it's desirable to use a catch all route to simplify searching and deeply nested dynamic routes.