Comment placer un slug à la place de l'id dans une URL Gatsby ?

Maintenant que j'ai installé react-bootstrap et un peu designé mon blog, je m'attaque à une nouvelle problématique. J'ai suivi le tutoriel officiel de Gatsby, et ce dernier nous permet d'afficher les articles et catégories par $id dans l'url. Ce n'est pas très esthétique, ni très user friendly.
J'ai donc cherché comment rendre mes URL plus joli en intégrant des slugs dans ma base strapi. J'ai longuement bataillé et je vous livre le résultat de mes fouilles webologiques.
1. Coté Strapi, on slugify !
La première chose à faire est d'installer slugify dans notre API Strapi :
npm install slugify
Ensuite, il faut se connecte à notre interface Strapi, et dans le Content Types Builder, ajouter un champ à Articles que l'on appelle slug (format text simple).
On va ensuite créer un model de slug dans le fichier suivant : Path — ./api/article/models/Article.js
module.exports = {
lifecycles: {
async beforeCreate(data) {
strapi.log.debug("beforeCreate")
data.slug = slugify(data.title, {lower: true});
},
async beforeUpdate (params,data) {
strapi.log.debug("beforeUpdate")
data.slug = slugify(data.title, {lower: true});
},
},
};Enfin, il faut cliquer sur ajouter un article, puis sur configurer la vue et enfin sur slug. En placeholder, indiquer "Generated automatically based on the title" et décocher l'option modifiable. Enregistrer les modifications. Pour s'assurer que cela a bien fonctionné, mettre à jour vos article en modifiant un espace et les sauvegardant. Le slug devrait alors apparaître dans le champ correspondant.
J'ai fait la même manipulation pour les tags et les catégories. A la différence près que ces derniers ont un champ name et non title. Les fichiers models sont donc les suivants :
Path — ./api/article/models/Category.js Path — ./api/article/models/Tag.js
lifecycles: {
async beforeCreate(data) {
strapi.log.debug("beforeCreate")
data.slug = slugify(data.name, {lower: true});
},
async beforeUpdate (params,data) {
strapi.log.debug("beforeUpdate")
data.slug = slugify(data.name, {lower: true});
},
},
};Voilà côté backend, on est good :) Passons au frontend.
Côté Gatsby, on fetch par URL !
Alors là, ça a été pour moi très fastidieux! Il faut dire que je suis une petite nouvelle dans le monde GraphQL. Je ne me rappelle plus l'ordre dans lequel j'ai effectué les modifications mais commençons par le fichier gatsby-node.js :
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions
const result = await graphql(
`
{
articles: allStrapiArticle(sort: { fields: updated_at, order: DESC }) {
edges {
node {
strapiId
slug
}
}
}
categories: allStrapiCategory {
edges {
node {
strapiId
slug
}
}
}
tags: allStrapiTag {
edges {
node {
strapiId
slug
}
}
}
}
`
)
if (result.errors) {
throw result.errors
}
// Create blog articles pages.
const articles = result.data.articles.edges
const categories = result.data.categories.edges
const tags = result.data.tags.edges
articles.forEach((article, index) => {
createPage({
path: `/article/${article.node.slug}`,
component: require.resolve("./src/templates/article.js"),
context: {
slug: article.node.slug,
title: article.node.title,
},
})
})
categories.forEach((category, index) => {
createPage({
path: `/category/${category.node.slug}`,
component: require.resolve("./src/templates/category.js"),
context: {
slug: category.node.slug,
},
})
})
tags.forEach((tag, index) => {
createPage({
path: `/tag/${tag.node.slug}`,
component: require.resolve("./src/templates/tag.js"),
context: {
slug: tag.node.slug,
},
})
})
}
On a ainsi créé des pages par slugs et placés ces derniers dans le context.
Ensuite, dans le fichier index.js qui récupère l'intégralité des articles, on modifie la query en ajouter slug partout où l'on souhaite le récupérer (c'est à dire dans article, category et tag :
<StaticQuery
query={graphql`
query {
allStrapiArticle(sort: { fields: updated_at, order: DESC }) {
edges {
node {
strapiId
title
categories {
name
id
slug
}
tags {
id
name
slug
}
image {
publicURL
}
updated_at
slug
}
}
}
}
`}
render={data => (
<ArticlesComponent
articles={data.allStrapiArticle.edges}
key={`${data.allStrapiArticle.edges.length}_articles`}
/>
)}
/>On pense bien sûr à changer tous les liens dans l'affichage en remplaçant id ou strapiId par slug (par exemple ici dans components/articles.js :
<Card
seo={seo}
className="h-100"
key={`article_card_${article.node.strapiId}`}
>
<a href={`/article/${article.node.slug}`}>
<Card.Img
variant="top"
src={article.node.image.publicURL}
alt={article.node.image.publicURL}
/>
</a>
<Card.Body>
<a href={`/article/${article.node.slug}`}>
<Card.Title className="h3">{article.node.title}</Card.Title>
</a>
<Card.Text>
<br />
<a href={`/article/${article.node.slug}`}>
<Button>LIRE L'ARTICLE</Button>
</a>
</Card.Text>
</Card.Body>
<Card.Footer>
<div className="footer-card-meta">
<div className="footer-category">
<IoMdPricetag />{" "}
{article.node.categories.map(category => (
<a
href={`/category/${category.slug}`}
alt={category.name}
key={`article_${article.node.strapiId}_cat_${category.id}`}
>
{category.name}{" "}
</a>
))}
</div>
<div className="footer-category">
<RiTimeLine />{" "}
<Moment format="DD/MM/YYYY">{article.node.updated_at}</Moment>
</div>
</div>
</Card.Footer>
</Card>Il faut également modifier le HeaderNav, qui contient la navigation vers les catégories :
<StaticQuery
query={graphql`
query {
allStrapiCategory(sort: { fields: name, order: ASC }) {
edges {
node {
strapiId
name
slug
}
}
}
}
`}
render={data =>
data.allStrapiCategory.edges.map((category, i) => {
return (
<Nav.Link
key={category.node.strapiId}
href={`/category/${category.node.slug}`}
>
{category.node.name}
</Nav.Link>
)
})
}
/>Et enfin, les templates ! Voici les queries, mais pensez bien à modifier tous vos liens dans vos vos render ;)
article.js :
export const query = graphql`
query ArticleQuery($slug: String!) {
strapiArticle(slug: { eq: $slug }) {
strapiId
title
content
updated_at
image {
publicURL
}
categories {
name
id
slug
}
tags {
name
id
slug
}
slug
}
}
`category.js :
export const query = graphql`
query Categories($slug: String!) {
articles: allStrapiArticle(
filter: { categories: { elemMatch: { slug: { eq: $slug } } } }
sort: { fields: updated_at, order: DESC }
) {
edges {
node {
strapiId
title
categories {
id
name
slug
}
image {
publicURL
}
slug
}
}
}
categories: strapiCategory(slug: { eq: $slug }) {
name
}
}
`tag.js :
export const query = graphql`
query Tags($slug: String!) {
articles: allStrapiArticle(
filter: { tags: { elemMatch: { slug: { eq: $slug } } } }
sort: { fields: updated_at, order: DESC }
) {
edges {
node {
strapiId
title
categories {
id
name
slug
}
tags {
id
name
slug
}
image {
publicURL
}
slug
}
}
}
tags: strapiTag(slug: { eq: $slug }) {
name
}
}
`J'espère que cela vous auras aidé ;) Bon dev à vous !