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
})
)
})
}
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 works on my site:
https://jason.stallin.gs/projects/
https://jason.stallin.gs/projects/robotjs/
For me, I create /projects/ manually, then all of the children come from WordPress.
gatsby-node:
https://github.com/octalmage/jason.stallin.gs/blob/master/gatsby-node.js#L38
Projects page:
https://github.com/octalmage/jason.stallin.gs/blob/master/src/pages/projects.js#L1
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?
Most helpful comment
You're welcome!
You can try including this snippet in your
functions.phpfile, based on code done by our backend dev:This adds a new
pathfield to all page and post type nodes.Now you can access this new field in your graphql query:
Then you should replace all instances of
node.slugwithnode.pathin thepathparameter ofcreatePage: