Snipcart Crawling product failed in a nextjs app

370 Views Asked by At

I'm using snipcart in a nextjs with strapi app. I receive an error when processing a payment :

A 'cart-confirmation' error occured in Snipcart. Reason:
'product-crawling-failed' [Details] We have not been able to find item with id '59' at 'https://cmeditions.fr/products/selfless'. Please make sure the product is correctly defined.

and :

An attempt to create an order with invalid products has been made. Don't forget you can't add/update items attributes in the cart dynamically with javascript as it will cause crawling issues

It works when data-item-id is replaced by a static value but not with somthing like {product.id}

Here is my component [sulg].js :

const ProductPage = ({ product }) => {
  const router = useRouter()
  if (router.isFallback) {
    return <div>Loading product...</div>
  }

  return (
    <div className="carousel-container m-6 grid grid-cols-1 gap-4 mt-8 items-start">
      <Head>
        <title>{product.title}</title>
      </Head>
      <div className="rounded-t-lg pt-2 pb-2 m-auto h-auto">
        <EmblaCarousel {...product} />
      </div>
      <div className="w-full p-5 flex flex-col justify-between">
        <div>
          <h4 className="mt-1 font-semibold leading-tight truncate text-slate">
            <span className="uppercase text-lg font-bold">{product.title}</span>
            <br />
            {product.author}
          </h4>
          <LanguageDesc {...product} />
        </div>
        <div className="flex flex-col md:flex-row">
          <div className="mt-4 font-semibold leading-tight truncate text-slate">
            <p>{product.year}</p>
            <p>Format: {product.size}</p>
            <p>Printing: {product.technic}</p>
            <p>Paper: {product.medium}</p>
            <p>{product.page}</p>
            <p>{product.copies} copies</p>
            <p>{product.price}€</p>
          </div>
          {product.status === "published" ? (
            <button
              className="snipcart-add-item mt-4 ml-16 self-center bg-slate border border-gray-200 d hover:shadow-lg hover:bg-brique focus:outline-none text-white font-semibold leading-none py-2 px-4 w-20 h-20 rounded-full"
              data-item-id={product.id}
              data-item-price={product.price}
              data-item-url={router.asPath}
              data-item-description={product.description}
              data-item-image={getStrapiMedia(
                product.image.formats.thumbnail.url
              )}
              data-item-name={product.title}
              v-bind="customFields"
            >
              Add to cart
            </button>
          ) : (
            <div className="text-center mr-10 mb-1" v-else>
              <div
                className="p-2 bg-indigo-800 items-center text-indigo-100 leading-none lg:rounded-full flex lg:inline-flex"
                role="alert"
              >
                <span className="flex rounded-full bg-indigo-500 uppercase px-2 py-1 text-xs font-bold mr-3">
                  Coming soon...
                </span>
                <span className="font-semibold mr-2 text-left flex-auto">
                  This article is not available yet.
                </span>
              </div>
            </div>
          )}
        </div>
      </div>
    </div>
  )
}

export async function getStaticProps({ params }) {
  const product = await getProduct(params.slug)
  return { props: { product } }
}

export async function getStaticPaths() {
  const products = await getProducts()
  return {
    paths: products.map((_product) => {
      return {
        params: { slug: _product.slug },
      }
    }),
    fallback: true,
  }
}

export default ProductPage

EDIT

I tried to return a json to the validator, but I think it isn't right

/api/products.js

import { getProducts } from "../../utils/api"

// eslint-disable-next-line import/no-anonymous-default-export
export default async function (_req, res) {
  return new Promise((resolve) => {
    getProducts()
      .then((response) => {
        res.statusCode = 200
        res.setHeader("Content-Type", "application/json")
        res.setHeader("Cache-Control", "max-age=180000")
        res.end(JSON.stringify(response))
        resolve()
      })
      .catch((error) => {
        res.json(error)
        res.status(405).end()
        resolve() // in case something goes wrong in the catch block (as vijay commented)
      })
  })
}

api.js

export function getStrapiURL(path) {
  return `${
    process.env.NEXT_PUBLIC_STRAPI_API_URL || "http://localhost:1337"
  }${path}`
}

// Helper to make GET requests to Strapi
export async function fetchAPI(path) {
  const requestUrl = getStrapiURL(path)
  const response = await fetch(requestUrl)
  const data = await response.json()
  return data
}

export async function getCategories() {
  const categories = await fetchAPI("/categories")
  return categories
}

export async function getCategory(slug) {
  const categories = await fetchAPI(`/categories?slug=${slug}`)
  return categories?.[0]
}

export async function getProducts() {
  const products = await fetchAPI("/products")
  return products
}

export async function getProduct(slug) {
  const products = await fetchAPI(`/products?slug=${slug}`)
  return products?.[0]
}

export async function getLibrairies() {
  const librairies = await fetchAPI("/librairies")
  return librairies
}

export async function getLibrairie(slug) {
  const librairies = await fetchAPI(`/librairies?slug=${slug}`)
  return librairies?.[0]
}
2

There are 2 best solutions below

0
On

It finally wasn't a matter of JSON crawler but something to see with the domain on the snipcart dashboard. I had to put the vercel.app domain first and my own domain name as a subdomain. It worked when I changed this. This is the only explaination I got

2
On

It sounds like you will need to validate the orders with a JSON file that you can assign to the buy button with data-item-url. You can read the full guide here: https://docs.snipcart.com/v3/setup/order-validation#json-crawler.

Let me know if that helps!