GatsbyJS内部链接SEO优化及相关技术问题咨询
Context
I'm building a site with GatsbyJS and switched from onClick navigation to Gatsby's <Link> component for SEO (since crawlers can't parse onClick events). Here's my updated working code:
render={data => ( <div className='feed'> {data.allContentfulBlog.edges.map(edge => ( <Link className='card' style={{ backgroundImage: `linear-gradient( to bottom, rgba(10,10,10,0) 0%, rgba(10,10,10,0) 50%, rgba(10,10,10,0.7) 100%), url(${edge.node.featuredImage.fluid.src})` }} to={`/blog/${edge.node.slug}`}> {edge.node.category.map(category => ( <p className='card__category'>{category.title}</p> ))} <p className='card__title'>{edge.node.title}</p> </Link> ))} </div> )}
I had two initial questions, and now have three follow-up questions about the working code:
Initial Questions & Answers
Question 1: How to replace onClick with <a href> (or Gatsby's <Link>) and correctly use ${edge.node.slug}?
Answer:
Instead of relying on onClick for navigation, Gatsby's <Link> component is the ideal solution—it’s SEO-friendly (renders a standard <a> tag under the hood) and preserves Gatsby’s fast client-side routing.
First, import the component from Gatsby:
import { Link } from 'gatsby'
Then use the to prop to define your path, just like you did: <Link to={/blog/${edge.node.slug}}>. This generates a valid href attribute that search crawlers can read, fixing your original SEO concern.
Question 2: Can I add the category to the slug to create paths like example.com/travel/how-to-travel?
Answer:
Absolutely! You’ll need two key steps to make this happen:
- Update your
gatsby-node.jsconfiguration to generate pages with the category in the path. When creating blog pages, combine the category slug (or a cleaned-up title) with the post slug:exports.createPages = async ({ graphql, actions }) => { const { createPage } = actions const result = await graphql(` query { allContentfulBlog { edges { node { slug category { slug # Use this if your Contentful category has a slug field; otherwise, use title.toLowerCase().replace(/\s+/g, '-') } } } } } `) result.data.allContentfulBlog.edges.forEach(edge => { // Adjust [0] if your category is a single reference instead of an array const categorySlug = edge.node.category[0].slug createPage({ path: `${categorySlug}/${edge.node.slug}`, component: path.resolve('./src/templates/blog-post.js'), context: { slug: edge.node.slug, }, }) }) } - Update the
<Link>toprop to match the new path structure:<Link to={`/${edge.node.category[0].slug}/${edge.node.slug}`}>
Follow-Up Questions & Answers
Question 1: Why doesn't {edge.node.category.map(category => ( {category.title} ))} work directly?
Answer:
In JSX, you can’t return raw text values without wrapping them in a valid JSX element (like <p>, <span>, or a React fragment). Your initial attempt returns {category.title} as a raw value, which isn’t a valid node React can render.
The working version wraps the title in a <p className='card__category'>, which is a valid element. If you don’t need a visible wrapper, you can use a React fragment instead:
{edge.node.category.map(category => ( <> {category.title} </> ))}
Question 2: Why do I have to use edge.node.category.map to get category.title instead of calling it directly?
Answer:
This is because your category field in Contentful is set up as a multi-reference field (meaning a blog post can have multiple categories). Gatsby fetches this as an array of category objects, not a single object.
If you only ever want one category per post, adjust your Contentful content model to make category a single reference instead of multiple. Then you could access it directly: edge.node.category.title (no map needed). But since it’s an array right now, you have to iterate over it to access each category’s title.
Question 3: What's the purpose of the key={edge.node.id} attribute I removed?
Answer:
The key prop is critical for rendering lists in React. React uses keys to track which list items have changed, been added, or removed—this optimizes re-renders and avoids unnecessary DOM updates.
Even though your code works without it, React will throw a console warning because it can’t uniquely identify each list item. You should add it back to your <Link> component (the top-level element in your map loop):
<Link key={edge.node.id} className='card' to={`/blog/${edge.node.slug}`}> {/* rest of your content */} </Link>
Always use a unique, stable identifier (like the Contentful id field) for keys—avoid array indexes, since they can change if the list order updates.
内容的提问来源于stack exchange,提问作者Jose

