You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

GatsbyJS内部链接SEO优化及相关技术问题咨询

GatsbyJS: SEO-Friendly Routing & Component Questions

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

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:

  1. Update your gatsby-node.js configuration 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,
          },
        })
      })
    }
    
  2. Update the <Link> to prop 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:32:18