Gatsby: Nest URLs in Gatsby (Wordpress Backend)

Created on 3 Aug 2018  路  8Comments  路  Source: gatsbyjs/gatsby

Hi. I can't wrap my head around how to nest URLs in Gatsby. I'm using Wordpress as a backend using Custom Post Types. I want my URLs to be (translated to English):

artists/artist-name/

But I can only access them as

/artist-name/

I've tried adding a src/pages/artists folder with a index.js and artist-profile.js file but it still doesn't work.

The Wordpress artist pages have /artists/ added to their path, but when I receive the slug in createPage it only shows /artist-name.

This is my gatsby-node.js setup:

const path = require("path");

exports.createPages = ({ graphql, boundActionCreators }) => {
  const { createPage } = boundActionCreators
  return new Promise((resolve, reject) => {

    // Templates
    const artisterTemplate = path.resolve(`./src/pages/artister/index.js`)
    const artistProfilTemplate = path.resolve(`./src/pages/artister/artist-profil.js`)

    resolve(
      graphql(
        `
        {
          allWordpressPage {
            edges {
              node {
                id
                title
                slug
              }
            }
          }
          allWordpressWpArtist {
            edges {
              node {
                id
                title
                slug
              }
            }
          }
        }
      `
      ).then(result => {
        if (result.errors) {
          reject(result.errors)
        }

        // Create artists overview page.
        result.data.allWordpressPage.edges.forEach(({ node }) => {
          createPage({
            path: node.slug, // required
            component: artisterTemplate,
            context: {
              slug: node.slug
            },
          })
        });

        // Create artist profile pages.
        result.data.allWordpressWpArtist.edges.forEach(({ node }) => {
          createPage({
            path: node.slug, // required
            component: artistProfilTemplate,
            context: {
              slug: node.slug
            },
          })
        });

        return
      })
    )
  })
}
question or discussion

Most helpful comment

You're welcome!

You can try including this snippet in your functions.php file, based on code done by our backend dev:

function custom_rest_api_init()
{
    remove_action('rest_api_init', 'wp_oembed_register_route');

    $post_types = get_post_types();

    foreach ($post_types as $post_type) {
        add_custom_rest_fields($post_type);
    }
}

add_action('init', 'custom_rest_api_init');

function add_custom_rest_fields($post_type)
{
    register_rest_field(
        $post_type,
        'path',
        array(
            'get_callback'    => 'get_parsed_url_path',
            'update_callback' => null,
            'schema'          => null,
        )
    );
}

function get_parsed_url_path($object)
{
    $post_id = $object['id'];
    return get_path($post_id);
}

function get_path($post)
{
    return wp_parse_url(get_permalink($post), PHP_URL_PATH);
}

This adds a new path field to all page and post type nodes.

Now you can access this new field in your graphql query:

graphql(
  `
  {
    allWordpressPage {
      edges {
        node {
          id
          title
          slug
          path
        }
      }
    }
    allWordpressWpArtist {
      edges {
        node {
          id
          title
          slug
          path
        }
      }
    }
  }
  `
)

Then you should replace all instances of node.slug with node.path in the path parameter of createPage:

createPage({
  path: node.path,
  component: artisterTemplate,
  context: {
    slug: node.slug
  },
})

All 8 comments

You need to pass the entire path to the path parameter in createPage. For example:

createPage({
  path: `/artists/${node.slug}/`,
  component: artistProfilTemplate,
  context: {
    slug: node.slug
  }
})

A better, more programmatic approach could be stripping the domain from the link field instead of using slug, or adding a custom path field to the Wordpress REST api. That way the paths will match those in Wordpress.

@nik-s Thanks for your response, it worked perfectly.

How would you go about the more programmatic approach if you happen to have any prior experience with this?

You're welcome!

You can try including this snippet in your functions.php file, based on code done by our backend dev:

function custom_rest_api_init()
{
    remove_action('rest_api_init', 'wp_oembed_register_route');

    $post_types = get_post_types();

    foreach ($post_types as $post_type) {
        add_custom_rest_fields($post_type);
    }
}

add_action('init', 'custom_rest_api_init');

function add_custom_rest_fields($post_type)
{
    register_rest_field(
        $post_type,
        'path',
        array(
            'get_callback'    => 'get_parsed_url_path',
            'update_callback' => null,
            'schema'          => null,
        )
    );
}

function get_parsed_url_path($object)
{
    $post_id = $object['id'];
    return get_path($post_id);
}

function get_path($post)
{
    return wp_parse_url(get_permalink($post), PHP_URL_PATH);
}

This adds a new path field to all page and post type nodes.

Now you can access this new field in your graphql query:

graphql(
  `
  {
    allWordpressPage {
      edges {
        node {
          id
          title
          slug
          path
        }
      }
    }
    allWordpressWpArtist {
      edges {
        node {
          id
          title
          slug
          path
        }
      }
    }
  }
  `
)

Then you should replace all instances of node.slug with node.path in the path parameter of createPage:

createPage({
  path: node.path,
  component: artisterTemplate,
  context: {
    slug: node.slug
  },
})

This is the first approach @nik-s described.

@nik-s That's quite awesome.

To me it seems less error prone. I'll try it out!

Sharing this additional info in case it helps someone out.

Props (*_giggle_*) to @nik-s for sharing that function.

I was able to generate my pages with that addition to my WP functions.php, but my next issue was: how to get the Gatsby website's menu to generate the correct nested URL paths.

I've got the _WP REST API Menus_ plugin installed in my WP backend so I can dynamically generate the main menu of my Gatsby site from the main-nav configured in WP (as per video 11 of Scott Tolinski's course on Gatsby and Headless WordPress).

I had to include the url to the query in the layout.js

wordpressWpApiMenusMenusItems(slug: { eq: "main-nav" }) {
  items {
    title
    object_id
    object_slug
    url
    wordpress_children {
      title
      object_id
      object_slug
      url
    }
  }
}

Then, to remove the baseUrl from each path, I did a little JS trickery with the absolute URL of each menu item in my menu in my header.js component. I split the URL at each / and removed everything up to and including the third / from the left (with a little help from this Stack Overflow post).

<nav>
  {menu.map(item => (
    <ul className="mainnav">
      <li key={item.object_id} className="mainnav__item">
        <Link
        // Here is the trickery
          to={item.url
            .split('/')
            .slice(3)
            .join('/')}
          activeClassName="mainnav__active"
        >
          <span
            dangerouslySetInnerHTML={{
              __html: item.title,
            }}
          />
        </Link>
        <ul className="mainnav__submenu">
          {item.wordpress_children &&
            item.wordpress_children.map(subitem => (
              <li key={item.object_id} className="mainnav__submenu__item">
                <Link
                // Here is the submenu trickery
                  to={subitem.url
                    .split('/')
                    .slice(3)
                    .join('/')}
                  activeClassName="mainnav__active"
                >
                  <span
                    dangerouslySetInnerHTML={{
                      __html: subitem.title,
                    }}
                  />
                </Link>
              </li>
            ))}
        </ul>
      </li>
    </ul>
  ))}
</nav>

Now the paths in the Gatsby website menu can be administered by the WordPress backend and will reflect the permalink on each WP post, even if it is a nested URL.

@SimonIMG Do you find it necessary to dangerouslySetInnerHTML for link titles? Are they more than plaintext?

Was this page helpful?
0 / 5 - 0 ratings