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

React项目中Shopify Storefront API单商品页403 Forbidden问题求助

React调用Shopify Storefront API获取单商品报403错误排查

我在React项目中调用Shopify Storefront API获取单商品数据时出现POST 403 (forbidden)错误,但商品列表页请求完全正常,求帮忙排查问题。

相关代码:

shopifyApi.jsx(Products.jsx引入,请求正常)

export const fetchProducts = async () => {
  const response = await fetch(`https://<MyShopifyStore/>.myshopify.com/api/2024-10/graphql.json`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Shopify-Storefront-Access-Token': ''
    },
    body: JSON.stringify({
      query: `
        {
          products(first: 5) {
            edges {
              node {
                id
                title
                descriptionHtml
                images(first: 1) {
                  edges {
                    node {
                      src
                    }
                  }
                }
                variants(first: 1) {
                  edges {
                    node {
                      priceV2 {
                        amount
                      }
                    }
                  }
                } 
              }
            }
          }
        }
      `,
    }),
  });

  const { data } = await response.json();
  return data.products.edges;
};

Products.jsx(请求及展示正常)

import { fetchProducts } from '../../../api/shopifyApi.js'

const Products = () => {
  const [productsList, setProductsList] = useState([]);
  
  useEffect(() => {
    const getProducts = async () => {
      const productData = await fetchProducts();
      setProductsList(productData)
    };
    getProducts();
  }, []);
  
  return (
    <div className="product-container">
      <div className='product-grid'>
      {
        productsList.map(({ node }) => (
          <div
            className='product-card' 
            key={node.id}
          >
            <Link
              to={`/product/${node.productId}`}
              reloadDocument
            >
              {node.images.edges.length > 0 && (
                <img 
                  src={node.images.edges[0].node.src} 
                  alt={node.title} 
                  width={300}
                />
              )}
              {/* <div dangerouslySetInnerHTML={{ __html: node.descriptionHtml }} /> */}
              <h2>{node.title}</h2>
            </Link>
            <p>£{node.variants.edges[0].node.priceV2.amount}</p>
            <button type='button'>ADD TO CART</button>
          </div>
        ))
      }
      </div>
    </div>
  )
}

export default Products

ProductPage.jsx(请求报错403)

const ProductPage = () => {
  const { productId } = useParams();
  const [product, setProduct] = useState(null);

  useEffect(() => {
    const fetchProduct = async () => {
      const response = await fetch(`https://<MyShopifyStore/>.myshopify.com/api/2024-10/graphql.json`, {
        method: 'POST',
        header: {
          'Content-Type': 'application/json',
          'X-Shopify-Storefront-Access-Token': ''
        },
        body: JSON.stringify({
          query: `
            query {
              product(id: "gid//shopify/Product/${productId}") {
                edges {
                  node {
                    id
                    title
                    descriptionHtml
                    images(first: 1) {
                      edges {
                        node {
                          src
                        }
                      }
                    }
                  }
                }
              }
            }
          `
        })
      })

      const { data } = await response.json();
      setProduct(data?.product?.edges)
    };

    fetchProduct();
  }, [productId])

  if (!product) return <div>Loading...</div>

  return (
    <div className='product-page-container'>
      <h1>{product.title}</h1>
      {product.images.edges.length > 0 && (
        <img src={product.images.edges[0].node.src} alt={product.title} />
      )}
      <div dangerouslySetInnerHTML={{ __html: product.descriptionHtml }}/>
    </div>
  )
}

export default ProductPage;

错误分析及解决方案

1. 请求头拼写错误

ProductPage.jsx的fetch配置中写了header而非正确的headers(少了末尾的s),导致请求头未正确携带授权令牌和内容类型,Shopify API直接拒绝未授权请求返回403。
修正代码:

headers: {
  'Content-Type': 'application/json',
  'X-Shopify-Storefront-Access-Token': '' // 确保此处填入有效的Storefront令牌
},

2. GraphQL查询格式错误

  • 单个product查询返回的是节点对象,而非包含edges的结构,你的查询写法product { edges { node {...} } }不符合API规范。
  • gid格式错误,正确格式是gid://shopify/Product/xxx,你写的是gid//shopify/Product/xxx(少了一个冒号),导致查询ID无效。

修正后的查询:

query {
  product(id: "gid://shopify/Product/${productId}") {
    id
    title
    descriptionHtml
    images(first: 1) {
      edges {
        node {
          src
        }
      }
    }
  }
}

3. 状态赋值与渲染逻辑错误

修正查询后,data.product是单个商品节点,不是edges数组,需要调整状态赋值和渲染逻辑:

// 状态赋值
setProduct(data?.product)

// 渲染部分
return (
  <div className='product-page-container'>
    <h1>{product.title}</h1>
    {product.images.edges.length > 0 && (
      <img src={product.images.edges[0].node.src} alt={product.title} />
    )}
    <div dangerouslySetInnerHTML={{ __html: product.descriptionHtml }}/>
  </div>
)

4. 路由参数传递错误

Products.jsx中传递的node.productId字段不存在,列表查询返回的节点只有id(格式为gid://shopify/Product/xxx),导致ProductPage拿到的productId无效。
修正路由参数传递:

// Products.jsx中提取ID部分传递
<Link
  to={`/product/${node.id.split('/').pop()}`}
  reloadDocument
>

内容的提问来源于stack exchange,提问作者ryanl

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 15:28:09