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

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.