Nextjs Dynamic nested paths

I have nested paths where I want a child page to be a dynamic page so that the content team can add it.

Structure:

Resources
  - Blog (dynamic page)
     - Blog pages (dynamic page)
  - Getting Started (dynamic page)
     - Pages (dynamic page)
- etc

How to set up files under app/ folder Page Type and link resolver all together?

Thank you in advance!

To achieve the dynamic page structure with Prismic and Next.js using the new app folder, you'll need to set up your routes dynamically while also configuring a link resolver to manage the relationships between pages and the dynamic content in Prismic.

Here’s a step-by-step guide to achieve this:

1. Setting up dynamic routes under the app directory

In the new Next.js 13 app folder structure, each folder corresponds to a route. Dynamic routes can be set using square brackets.

  • Dynamic Parent Page (e.g., blog)
    • Create the following structure inside your app directory:
      app/
        resources/
          blog/
            [uid]/
              page.js
            page.js
          getting-started/
            [uid]/
              page.js
            page.js
      
    • Each [uid] will correspond to the dynamic blog or getting started pages.

2. Creating the dynamic page for blog

In app/resources/blog/[uid]/page.js you can fetch the specific content from Prismic using the uid. Here’s an example:

// app/resources/blog/[uid]/page.js

import { createClient } from '../../../prismicio'; // Adjust path according to your prismic setup
import { PrismicRichText } from '@prismicio/react';

export default async function BlogPostPage({ params }) {
  const client = createClient();

  // Fetch the document using the uid
  const blogPost = await client.getByUID('blog_post', params.uid);

  return (
    <div>
      <h1>{blogPost.data.title}</h1>
      <PrismicRichText field={blogPost.data.content} />
    </div>
  );
}

This page will dynamically load content based on the uid in the URL, using Prismic's getByUID.

3. Creating the dynamic parent page (e.g., blog)

In app/resources/blog/page.js you can list all the blog posts:

// app/resources/blog/page.js

import { createClient } from '../../../prismicio';
import Link from 'next/link';

export default async function BlogPage() {
  const client = createClient();

  // Fetch all blog posts
  const blogPosts = await client.getAllByType('blog_post');

  return (
    <div>
      <h1>Blog</h1>
      <ul>
        {blogPosts.map((post) => (
          <li key={post.id}>
            <Link href={`/resources/blog/${post.uid}`}>
              {post.data.title}
            </Link>
          </li>
        ))}
      </ul>
    </div>
  );
}

4. Set up the Link Resolver

Prismic’s link resolver allows you to manage links between documents. You can create a link-resolver.js to manage dynamic paths, especially for content types like blog_post or page.

// link-resolver.js

export const linkResolver = (doc) => {
  if (doc.type === 'blog_post') {
    return `/resources/blog/${doc.uid}`;
  }
  if (doc.type === 'getting_started') {
    return `/resources/getting-started/${doc.uid}`;
  }
  return '/';
};

5. Update Prismic Client to use Link Resolver

You’ll need to integrate the linkResolver into the Prismic client setup. Here’s how to do it inside prismicio.js (or wherever you’ve set up your Prismic client):

import * as prismic from '@prismicio/client';
import { linkResolver } from './link-resolver';

export const createClient = () => {
  const client = prismic.createClient(process.env.NEXT_PUBLIC_PRISMIC_ENDPOINT, {
    accessToken: process.env.PRISMIC_ACCESS_TOKEN,
  });

  // Add the link resolver
  client.linkResolver = linkResolver;

  return client;
};

6. Handling additional dynamic paths

For the getting-started and other nested sections, follow the same pattern as with blog. Adjust the content types and page structures accordingly.

For example, for a "Getting Started" dynamic page, create the structure:

app/
  resources/
    getting-started/
      [uid]/
        page.js
      page.js

Recap of Structure:

app/
  resources/
    blog/
      [uid]/    -> Dynamic blog post page
      page.js    -> Blog listing page
    getting-started/
      [uid]/    -> Dynamic getting-started page
      page.js    -> Getting started listing page

Each dynamic route will correspond to a page type in Prismic, and your link-resolver will generate the correct paths.

This structure allows your content team to manage both the parent and child dynamic pages easily within Prismic while maintaining clear routing in Next.js.

Thank you for the info.

How about the path in const routes: prismic.ClientConfig["routes"] function in prismicio.ts? Do you have examples?

Certainly! Here’s an example of a route resolver for Prismic in Next.js. The route resolver is slightly different from the link resolver, as it provides more control over URL generation, especially for nested or more complex URL structures.

Prismic Route Resolver Example

The routeResolver allows you to define more complex paths for your documents, such as when you have nested or hierarchical structures like you described (e.g., resources/blog/[slug]).

Here's an example of how you can set up a route resolver for your blog_post, getting_started, and other content types.

1. Create the Route Resolver

Create a new file called route-resolver.js in your project:

// route-resolver.js

export const routeResolver = {
  routes: [
    {
      type: 'blog_post',  // Prismic document type
      path: '/resources/blog/:uid',  // Route path in Next.js
    },
    {
      type: 'getting_started',  // Prismic document type
      path: '/resources/getting-started/:uid',  // Route path for getting started pages
    },
    {
      type: 'other_page_type',  // Example of another page type
      path: '/resources/:uid',  // Another path for dynamic content
    }
  ],
};

2. Update the Prismic Client to Use the Route Resolver

You need to modify your Prismic client configuration to use the routeResolver. Update your prismicio.js file (or wherever you've set up the Prismic client) to import and use the routeResolver.

// prismicio.js

import * as prismic from '@prismicio/client';
import { routeResolver } from './route-resolver';

export const createClient = () => {
  const client = prismic.createClient(process.env.NEXT_PUBLIC_PRISMIC_ENDPOINT, {
    accessToken: process.env.PRISMIC_ACCESS_TOKEN,
  });

  // Add the route resolver for more complex routing
  client.routes = routeResolver.routes;

  return client;
};

3. Using Route Resolver in Components

Once the route resolver is set up, you don’t need to manually build the paths using the document uid in your components. Instead, Prismic will automatically resolve the correct paths based on the routes you defined in the route resolver.

For example, when linking to a document, you can use the prismicH.asLink helper function, which will automatically use the correct path defined by the routeResolver.

Example in a component that lists blog posts:

import Link from 'next/link';
import { asLink } from '@prismicio/helpers';  // Helper from Prismic to resolve links

export default function BlogPostList({ blogPosts }) {
  return (
    <ul>
      {blogPosts.map((post) => (
        <li key={post.id}>
          {/* Use the asLink helper to automatically resolve the correct path */}
          <Link href={asLink(post)}>
            {post.data.title}
          </Link>
        </li>
      ))}
    </ul>
  );
}

In this case, the asLink(post) will resolve the link using the routeResolver, and for a blog_post document, it will generate a URL like /resources/blog/[uid].

4. Testing and Deployment

With the route resolver set up, you can now deploy your Next.js app and test the dynamic routing. When your content team creates a new blog_post or getting_started page in Prismic, it will automatically generate the correct URL using the route resolver and display the content based on the uid.

Summary

  1. Create the route-resolver.js file that defines how the routes are resolved based on the document type.
  2. Update the Prismic client to use the routeResolver.
  3. Use Prismic’s asLink or asLinkResolver functions in your components to automatically resolve URLs based on the defined routes.

This setup ensures that you can handle nested dynamic pages and have complete control over the paths, making it easy for your content team to add new pages.

Thank you, @Phil I will try to implement it and get back to you if I am stuck.

Can your team make a video tutorial on YouTube? This is a very complex build and I believe it will benefit most people.

1 Like

We already have documentation for this use case:

And our Youtube channel has lots of tutorials:

But I agree a video for directly this case in Next.js would be nice :slight_smile:

Hello @Phil,

Based on the Define Paths link you provided and specifically the Nested paths section, what if the blog in the example above wasn't a list page that pulls back all blog_posts but instead its own page type? How would you route to those pages while still having nested blog_post page types?

The structure for blog_post:

app/
  resources/
    [blog]/
      [uid]/
        page.js    -> Dynamic blog post page

The route-resolver for blog_post:

// route-resolver.js

export const routeResolver = {
  routes: [
    {
      type: 'blog_post',  // Prismic document type
      resolvers: {
        blog: 'blog',
      },
      path: '/resources/:blog/:uid',  // Route path in Next.js
    }
};

Hi @jplate,

Welcome to the community!

Can you give me a little more information on what you're trying to do, and what's not working? Do you mean if Blog isn't a dynamic page listing the blog_posts like in the example above? What do you want it to be doing? :slight_smile:

Hello,

Correct, we are wondering if the Blog isn't a dynamic page listing blog_posts but instead, a page created in Prismic with its own uid and unique page type.

So in theory we are looking for something like:

app/
  resources/
    [blog]/
      [uid]/page.js  -> Dynamic blog post page
      page.js -> Dynamic blog page, not a listing page

The route could look like:

// route-resolver.js

export const routeResolver = {
  routes: [
    {
      type: 'blog',  // Prismic document type
      path: '/resources/:blog',  // Route path in Next.js
    }
    {
      type: 'blog_post',  // Prismic document type
      resolvers: {
        blog: 'blog',
      },
      path: '/resources/:blog/:uid',  // Route path in Next.js
    }
};

Obviously, this doesn't work as it is written here because the blog page type route is expecting a :uid and not a :blog resolver. Is there a way with App Routing to nest page types?

Have you created the content relationship field called blog in your blog_post documents?

Yes, but how does that help with the blog page type?

If I try to build my project using the path /resources/:blog on the blog type, I get this error:
[Link resolver error] The following resolvers are missing for page type blog: - blog

By default, the only variables allowed in the path pattern are :uid and :lang. So I need a way to point /resources/:uid to /resources/:blog. Or do I have to add a blog content relationship field to the blog page type and the editor has to select the same page they are editing?

Yes you have to add a blog content relationship to the blog_post page type in the top of the type. The editor has to then link to a document of the type blog.

Nope, that's not what I'm asking. I know I have to add it to the blog_post page type

Do I have to add a blog content relationship field to the blog page type?

Hey @jplate ,

I gave @Ezekiel wrong information for this. You don't to add a blog content relationship field to the blog page type.

You just need to update your route resolver as so, the uid is unique to each type so it can referenced in each route.

// route-resolver.js

export const routeResolver = {
  routes: [
    {
      type: 'blog',  // Prismic document type
      path: '/resources/:uid',  // Route path in Next.js
    }
    {
      type: 'blog_post',  // Prismic document type
      resolvers: {
        blog: 'blog',
      },
      path: '/resources/:blog/:uid',  // Route path in Next.js
    }
};

Hello @Phil

Correct me if I'm wrong, but doesn't that route resolver require the app routing folder structure to look like:

app/
  resources/
    [blog]/
      [uid]/page.js  -> Dynamic blog post page
    [uid]/
      page.js -> Dynamic blog page, not a listing page

If done this way, we get an error saying we can't use different slug names for the same dynamic path. Meaning, [blog] and [uid] can't be used at the same level.

So you could have the following folder structure:

app/
  resources/
    [blog]/
      page.js  -> Dynamic blog post page
      [uid]/
        page.js -> Dynamic blog page, not a listing page

Your route resolver would look like the one I showed you above.

resources/[blog]/page.js Would have the follow code:

export default async function Page({ params }) {
  const client = createClient();
  const page = await client
    .getByUID("blog", params.blog)
    .catch(() => notFound());

  return <SliceZone slices={page.data.slices} components={components} />;
}

export async function generateStaticParams() {
  const client = createClient();

  const pages = await client.getAllByType("blog");

  return pages.map((page) => {
    return { blog: page.uid };
  });
}

and resources/[blog]/page.js/[uid]/page.js would have the follow code:

export default async function Page({ params }) {
  const client = createClient();
  const page = await client
    .getByUID("blog_post", params.uid)
    .catch(() => notFound());

  return <SliceZone slices={page.data.slices} components={components} />;
}

export async function generateStaticParams() {
  const client = createClient();

  const pages = await client.getAllByType("blog_post");

  return pages.map((page) => {
    return {
      blog: page.data.blog.uid,
      uid: page.uid,
    }
  })
}

Try this out and let me know how it goes.

Thank you, @Phil!

That was the missing link, the parameter on the [blog]/page.js needed to be updated from params.uid to params.blog.