Gatsby: Create URL for nested pages

Created on 9 Feb 2018  路  10Comments  路  Source: gatsbyjs/gatsby

I've created nested pages in Wordpress (eg. example.com/parent_page/child_page) and I want to reflect same structure using Gatsby.
Want is a proper solution for that? I've tried to create a complex query but without success...

question or discussion

Most helpful comment

If You want to do it entirely on gatsby side of things - you can:
in your gatsby-node.js add:

const _ = require(`lodash`);

exports.sourceNodes = ({ getNodes, boundActionCreators }) => {
  const { createNodeField } = boundActionCreators;
  const pageNodes = getNodes().filter(
    node => node.internal.type === "wordpress__PAGE"
  );

  pageNodes.forEach(pageNode => {
    let pathFragments = [];
    let tmpNode = pageNode;
    do {
      pathFragments.push(tmpNode.slug);
      tmpNode = pageNodes.find(
        node => node.wordpress_id === tmpNode.wordpress_parent
      );
    } while (tmpNode);

    const path = pathFragments.reverse().join("/");
    createNodeField({
      node: pageNode,
      name: `path`,
      value: path
    });
  });
};

and then you can query:

query allPosts {
  allWordpressPage {
    edges {
      node {
        slug
        title
        fields {
          path
        }
      }
    }
  }
}

which will give you:

{
  "data": {
    "allWordpressPage": {
      "edges": [
        {
          "node": {
            "slug": "test",
            "title": "Test",
            "fields": {
              "path": "sample-page/test"
            }
          }
        },
        {
          "node": {
            "slug": "sample-page",
            "title": "Sample Page",
            "fields": {
              "path": "sample-page"
            }
          }
        }
      ]
    }
  }
}

All 10 comments

I've added this piece of code to my wordpress instance:

function custom_rest() {
  register_rest_field(array('page'), 'path', array(
    'get_callback' => function ($post) {
      return get_page_uri($post['id']);
    }
  ));
}
add_action('rest_api_init', 'custom_rest');

This will add field path to Your wordpress pages data you can use to construct gatsby paths - using your example it will return parent_page/child_page

I want to avoid adding additional code to my Wordpress instance.
I've wanted to do it with one query but I gave up. I will try use regexp to get a path from link field.

Nevertheless, thank you for your help :)

If You want to do it entirely on gatsby side of things - you can:
in your gatsby-node.js add:

const _ = require(`lodash`);

exports.sourceNodes = ({ getNodes, boundActionCreators }) => {
  const { createNodeField } = boundActionCreators;
  const pageNodes = getNodes().filter(
    node => node.internal.type === "wordpress__PAGE"
  );

  pageNodes.forEach(pageNode => {
    let pathFragments = [];
    let tmpNode = pageNode;
    do {
      pathFragments.push(tmpNode.slug);
      tmpNode = pageNodes.find(
        node => node.wordpress_id === tmpNode.wordpress_parent
      );
    } while (tmpNode);

    const path = pathFragments.reverse().join("/");
    createNodeField({
      node: pageNode,
      name: `path`,
      value: path
    });
  });
};

and then you can query:

query allPosts {
  allWordpressPage {
    edges {
      node {
        slug
        title
        fields {
          path
        }
      }
    }
  }
}

which will give you:

{
  "data": {
    "allWordpressPage": {
      "edges": [
        {
          "node": {
            "slug": "test",
            "title": "Test",
            "fields": {
              "path": "sample-page/test"
            }
          }
        },
        {
          "node": {
            "slug": "sample-page",
            "title": "Sample Page",
            "fields": {
              "path": "sample-page"
            }
          }
        }
      ]
    }
  }
}

Perfect! That works for me :)
@pieh thank you! 馃憣

If You want to do it entirely on gatsby side of things - you can:
in your gatsby-node.js add:

const _ = require(`lodash`);

exports.sourceNodes = ({ getNodes, boundActionCreators }) => {
  const { createNodeField } = boundActionCreators;
  const pageNodes = getNodes().filter(
    node => node.internal.type === "wordpress__PAGE"
  );

  pageNodes.forEach(pageNode => {
    let pathFragments = [];
    let tmpNode = pageNode;
    do {
      pathFragments.push(tmpNode.slug);
      tmpNode = pageNodes.find(
        node => node.wordpress_id === tmpNode.wordpress_parent
      );
    } while (tmpNode);

    const path = pathFragments.reverse().join("/");
    createNodeField({
      node: pageNode,
      name: `path`,
      value: path
    });
  });
};

and then you can query:

query allPosts {
  allWordpressPage {
    edges {
      node {
        slug
        title
        fields {
          path
        }
      }
    }
  }
}

which will give you:

{
  "data": {
    "allWordpressPage": {
      "edges": [
        {
          "node": {
            "slug": "test",
            "title": "Test",
            "fields": {
              "path": "sample-page/test"
            }
          }
        },
        {
          "node": {
            "slug": "sample-page",
            "title": "Sample Page",
            "fields": {
              "path": "sample-page"
            }
          }
        }
      ]
    }
  }
}

Hi @pieh thanks for the example above. Maybe I'm missing something but I'm facing some problem in that the query returns all pages/nodes. What If I want to display children of a particular page and so on. Example Page A {child a, b c..}, Page B {child a, b, c}. How do I make this happen when a page is generated to show only the child pages associated to it? Thanks!!

I figured a way to achieve this. For anyone interested, here's what you do:
You need to have ACF Pro installed,
Use either the Relationship or the Post Object field type.
In your pages or posts you can then make the relationship in there. One note of caution, do not reference the parent page in the relationship, use just the child pages you need.

@batguyz maybe you could use something like this:

  childrenPages: allWordpressPage(filter: {fields: {path: {glob: "sample-page/**"}}}) {
    edges {
      node {
        fields {
          path
        }
        frontmatter {
          title
          date
        }
      }
    }
  }

which should only return children of sample-page (if you have paths setup like that)

edit: you would probably need to create and pass that glob in gatsby-node when calling createPage so you could use variable in graphql query

Thanks @pieh I'll try that out.
Although in my current method works in returning parent/child concept, I've run into a little issue with getting the slug or url. I can only use the guid which is not ideal.

I'll keep you posted, thanks a lot!

Related, why doesn't this work?:

  query IndexQuery {
    allWordpressPage(filter: { wordpress_parent: {eq: 123} } ) {

I would expect this query, which doesn't seem to be the case:

/wp-json/wp/v2/pages?parent=123

edit: nevermind, that did work 鈥撀營 needed to rebuild the site. Leaving up for posterity.

Tangential, but you can also build a tree with something like the following:

    siblings: allWordpressPage(filter: { wordpress_parent: { eq: $wordpress_parent } } ) {}
    children: allWordpressPage(filter: { wordpress_parent: { eq: $wordpress_id } } ) {}

It would be nice if there was an out of box solution for querying the entire tree in one go.

boundActionCreators is now deprecated - I've modified the code with the new actions parameter

exports.sourceNodes = ({ getNodes, actions }) => {
  const { createNodeField } = actions;
  const pageNodes = getNodes().filter(
    node => node.internal.type === "wordpress__PAGE"
  );

  pageNodes.forEach(pageNode => {
    let pathFragments = [];
    let tmpNode = pageNode;
    do {
      pathFragments.push(tmpNode.slug);
      tmpNode = pageNodes.find(
        node => node.wordpress_id === tmpNode.wordpress_parent
      );
    } while (tmpNode);

    const path = pathFragments.reverse().join("/");
    createNodeField({
      node: pageNode,
      name: `path`,
      value: path,
    });
  });
};
Was this page helpful?
0 / 5 - 0 ratings

Related issues

Oppenheimer1 picture Oppenheimer1  路  3Comments

signalwerk picture signalwerk  路  3Comments

3CordGuy picture 3CordGuy  路  3Comments

brandonmp picture brandonmp  路  3Comments

magicly picture magicly  路  3Comments