Dynamic pages and nested children

I've been trying to follow Define Paths with Next.js - Prismic to implement something that should be very simple: namely dynamic routing.

In my case I have basic pages such as /about, /faq etc. and also pages related to cities /copenhagen and /munich for example. Now I want to add the possibility to create a city page, e.g. /copenhagen/apartments and for this I thought I could simply create a custom type called "city" and a custom type called a "city page" with a relationship to the city (imagine the "article" custom type from the article linked above, but instead of a category it's a city). Since I can't have both [city]/[uid].tsx and [uid].tsx with Next.js, I'm a bit lost on how to implement this.

Seems like NextJS - dynamic page and dynamic folder at root level - possible? is similar to my problem, but I am not satisfied with any of the answers given there. It seems the only way to do it is to namespace your folder routes (e.g. /cities/[city]/[uid]) but then I am not sure how to create a page such as /cities/copenhagen on the Prismic dashboard. Either way, the documentation is imo very lacking in this case (nothing mentioned about useGetStaticProps) and does not even work in some cases (const [section, category, uid] = doc.url.split('/') does not work since the url starts with a /, so it will not be split correctly).

Any suggestions are very much appreciated!

Hello @amos

Thanks for posting this question to us.

I remember I have discussed the same type of query before with one of my clients. This thread might be useful for you, it's in Nuxt.js Nuxt with SM Routes - #31 by Priyanka

Let me know if you have any doubts.

Thanks,
Priyanka

I'm not using Nuxt, but Next, which has file-system based router and is the cause of my problem. I also don't know how Vue's router works (the part with asyncData and querying the right UID), so not sure if this is really that useful to me, but I will have a look. Would be great if you could show me something related to Next.js as well, though!

Hello @amos ,

Can you try with the following file structure in next.js:

/pages
	index.js  —> url:/
	/cities
		index.js —> url:/cities
		/[id]
			index.js —>url:/cities/[city]
			/[id]
			index.js —> url:/cities/[city]/[uid]

Reference: Routing: Dynamic Routes | Next.js

Hope it helps.

Thanks,
Priyanka

Hi @Priyanka,

It's not necessarily the file structure that is the problem for me. It's mainly how I should set up the "City Page" custom type, the router in my Prismic configuration and how I should then query this in the Next.js page. I already did the following and it's sort of working, but I don't think it's the correct way of doing things:

// City Page custom type
"city" : {
  "type" : "Link",
  "config" : {
    "select" : "document",
    "customtypes" : [ "city" ],
    "label" : "City",
    "placeholder" : "city"
  }
},
"uid" : {
  "type" : "UID",
  "config" : {
    "label" : "UID",
    "placeholder" : "unique-identifier-eg-apartments"
  }
},

and the following file structure

/cities
  /[city]
    index.tsx —>url:/cities/[city]
    /[uid]
      index.tsx —> url:/cities/[city]/[uid]

I then have this in my Prismic configuration:

export const linkResolver = (doc) => {
  if (doc.type === "page") {
    return `/${doc.uid}`;
  }
  return "/";
};

export const Router = {
  routes: [
    {
      type: "page",
      path: "/:uid",
    },
    {
      type: "home-page",
      path: "/",
    },
    {
      type: "city-page",
      path: "/cities/:city?/:uid",
      resolvers: {
        city: "city",
      },
    },
  ],
  href: (type) => {
    const route = Router.routes.find((r) => r.type === type);
    return route && route.href;
  },
};

which is probably wrong, as I have to do the following to make it work:

// /cities/[city]/index.tsx
export const getStaticProps = useGetStaticProps({
  client: Client(),
  type: "city-page",
  apiParams({ params }: any) {
    return {
      uid: params.city,
    };
  },
});

// eslint-disable-next-line react-hooks/rules-of-hooks
export const getStaticPaths = useGetStaticPaths({
  client: Client(),
  type: "city-page",
  formatPath: (prismicDocument: any) => {
    return {
      params: {
        city: prismicDocument.uid,
      },
    };
  },
});
// /cities/[city]/[uid].tsx
export const getStaticProps = useGetStaticProps({
  client: Client(),
  type: "city-page",
  apiParams({ params }: any) {
    return {
      uid: params.uid,
    };
  },
});

// eslint-disable-next-line react-hooks/rules-of-hooks
export const getStaticPaths = useGetStaticPaths({
  client: Client(),
  type: "city-page",
  formatPath: (prismicDocument: any) => {
    if (!prismicDocument.data.city.slug) {
      return {
        params: {
          uid: prismicDocument.uid,
          city: prismicDocument.uid,
        },
      };
    }

    return {
      params: {
        uid: prismicDocument.uid,
        city: prismicDocument.data.city.slug,
      },
    };
  },
});

So for e.g. /cities/new-york I will create a City Page with just the uid field set to new-york, and for e.g. /cities/new-york/apartments I would create another page with the city field set to the City custom type New York and the uid set to apartments.

I am obviously missing something or doing it wrong, as the double return above does not make sense and I am only doing it because I get the following error if I don't: A required parameter (city) was not provided as a string in getStaticPaths for /cities/[city]/[uid]. If you could help me out with this, it would be very much appreciated!

Hello @amos

It will be better if you can share the Github project code and the URL of the Prismic repository?

Thanks,
Priyanka

Hi @Priyanka

The GitHub repository is private so I can't share that, but I think I have posted all the relevant code above already so that should be fine. The Prismic repositry is https://joinlifex.prismic.io/.

Hi @amos ,

First, I checked your repository and I couldn't find city-page documents.

If I understand your content structure correctly. You need to prepare 2 custom types:

  1. city: for the pages like NewYork
  2. city-page : For the pages like 'apartments'
    You create a content relationship in city-page with city for ex: for apartments, create a content-relationship here with city NewYork

Now on Front-end:
When you use /cities/:city?/:uid : it means that city param is optional here, not required. for making it required, you need to remove the ? after city.
so when we say:

Now there are 2 cases:

  1. When city is required- >/cities/:city/:uid
/pages
	index.js  —> url:/
	/cities
		/[:city]
			index.js —>url:/cities/[city]   ----> for pages like Newyork
			/[:uid]
			index.js —> url:/cities/[city]/[uid]   ----> for pages like Apartments

It means when we hit the URL with /cities/new-york/apartments, then under /cities/:city/:uid, you can get the city basic data using the content relationship fetch query.

  1. When city is optional- >/cities/:city?/:uid
    in this case, both URLs like /cities/apartments and /cities/newyork/apartments can behave the same way.

The best way is to have the content relationship between custom type city and city-page as mentioned above.

Let me know if you have any other doubts.

Thanks,
Priyanka

Hi,

As I mentioned before (Dynamic pages and nested children - #6 by amos ), I had done exactly what you suggest and it does not work properly when building pages, so I ended up changing it to have a "city home page" custom type (e.g. /newyork) and a "city page" custom type (e.g. /newyork/apartments). It is not an ideal solution, but it seems to work...

Hello @amos

Can you explain a bit more, please? Why is this setup is not working? Did you get any errors with the suggested setup?

Thanks,
Priyanka