How to add category schema to index page in Sanity e-commerce site?

17 replies
Last updated: Jul 7, 2022
Hi, I used this repo: https://github.com/adrianhajdin/ecommerce_sanity_stripe to build an ecommerce site's structure following his youtube tutorial. However, the only real difference is I decided to use some of the standard e-commerce schemas already built in rather than making my own schema as he did. My question is how could I take the structure of this site and add the category schema to the index page? I want to put categories on the home page rather than listing all of my products on the home page. Then when you click the category it brings you to a new page of the site that will only show products in that category. Which will then lead to the single products page. All of which is being hooked dynamically... If you click the repo you can scroll down to see images of the product page for reference.
Could someone pretty please help me type up something that will help me achieve this? I will post my exact schema and category files in the comments of this thread.
AI Update

I can help you restructure your e-commerce site to display categories on the home page instead of all products! This is a common pattern for e-commerce sites and works great with Sanity's flexible content structure.

Here's how to achieve this:

1. Schema Setup

First, make sure your product schema references categories. Your product schema should include a reference to categories:

// schemas/product.js
export default {
  name: 'product',
  title: 'Product',
  type: 'document',
  fields: [
    // ... other fields
    {
      name: 'category',
      title: 'Category',
      type: 'reference',
      to: [{type: 'category'}]
    }
  ]
}

2. Update Your Home Page (index.js)

Modify your getServerSideProps or getStaticProps to fetch categories instead of products:

// pages/index.js
import { client } from '../lib/client';

export const getStaticProps = async () => {
  // Fetch categories instead of products
  const categoriesQuery = '*[_type == "category"]';
  const categories = await client.fetch(categoriesQuery);

  return {
    props: { categories }
  }
}

const Home = ({ categories }) => (
  <div>
    <div className="products-container">
      {categories?.map((category) => (
        <Link href={`/category/${category.slug.current}`} key={category._id}>
          <div className="product-card">
            <img 
              src={urlFor(category.image)}
              width={250}
              height={250}
              className="product-image"
            />
            <p className="product-name">{category.name}</p>
          </div>
        </Link>
      ))}
    </div>
  </div>
);

3. Create a Dynamic Category Page

Create a new file pages/category/[slug].js to display products filtered by category:

// pages/category/[slug].js
import React from 'react';
import { client } from '../../lib/client';
import { Product } from '../../components'; // Reuse the Product component

const CategoryPage = ({ products, category }) => {
  return (
    <div>
      <div className="products-heading">
        <h2>{category.name}</h2>
        <p>{category.description}</p>
      </div>

      <div className="products-container">
        {products?.map((product) => (
          <Product key={product._id} product={product} />
        ))}
      </div>
    </div>
  );
};

export const getStaticPaths = async () => {
  const query = `*[_type == "category"] {
    slug {
      current
    }
  }`;
  
  const categories = await client.fetch(query);
  
  const paths = categories.map((category) => ({
    params: { 
      slug: category.slug.current
    }
  }));

  return {
    paths,
    fallback: 'blocking'
  }
}

export const getStaticProps = async ({ params: { slug } }) => {
  // Fetch the category details
  const categoryQuery = `*[_type == "category" && slug.current == '${slug}'][0]`;
  const category = await client.fetch(categoryQuery);
  
  // Fetch products that reference this category
  const productsQuery = `*[_type == "product" && references(*[_type=="category" && slug.current == '${slug}']._id)]`;
  const products = await client.fetch(productsQuery);

  return {
    props: { products, category }
  }
}

export default CategoryPage;

4. Keep Your Existing Product Page

Your existing pages/product/[slug].js should continue to work as-is, since you're just adding a new layer of navigation above it.

Key Points:

  • Home page (/) now shows categories
  • Category page (/category/[slug]) shows products filtered by that category
  • Product page (/product/[slug]) shows individual product details (unchanged)

The GROQ query *[_type == "product" && references(*[_type=="category" && slug.current == '${slug}']._id)] is doing the heavy lifting here - it finds all products that reference the category you're viewing.

You can reuse most of the styling and components from the adrianhajdin/ecommerce_sanity_stripe repo, just swap out what data you're fetching and displaying on each page. The Product component from the tutorial can be reused on your category pages to maintain consistent styling.

If you're using Next.js 13+ with the App Router instead of the Pages Router, the structure would be similar but using app/category/[slug]/page.js instead. Let me know your schema files and I can help you refine this further!

Show original thread
17 replies
Product

export default {
  name: 'product',
  title: 'Product',
  type: 'document',
  fields: [
    {
      name: 'title',
      title: 'Title',
      type: 'string',
    },
    {
      name: 'slug',
      title: 'Slug',
      type: 'slug',
      options: {
        source: 'title',
        maxLength: 96,
      },
    },
    {
      title: 'Default variant',
      name: 'defaultProductVariant',
      type: 'productVariant',
    },
    {
      title: 'Variants',
      name: 'variants',
      type: 'array',
      of: [
        {
          title: 'Variant',
          type: 'productVariant',
        },
      ],
    },
    {
      title: 'Tags',
      name: 'tags',
      type: 'array',
      of: [
        {
          type: 'string',
        },
      ],
      options: {
        layout: 'tags',
      },
    },
    {
      name: 'categories',
      title: 'Categories',
      type: 'array',
      of: [
        {
          type: 'reference',
          to: {type: 'category'},
        },
      ],
    },

  ],

  preview: {
    select: {
      title: 'title',
      media: 'defaultProductVariant.images[0]',
    },
  },
}
export default {
name: 'product',
title: 'Product',
type: 'document',
fields: [
{
name: 'title',
title: 'Title',
type: 'string',
},
{
name: 'slug',
title: 'Slug',
type: 'slug',
options: {
source: 'title',
maxLength: 96,
},
},
{
title: 'Default variant',
name: 'defaultProductVariant',
type: 'productVariant',
},
{
title: 'Variants',
name: 'variants',
type: 'array',
of: [
{
title: 'Variant',
type: 'productVariant',
},
],
},
{
title: 'Tags',
name: 'tags',
type: 'array',
of: [
{
type: 'string',
},
],
options: {
layout: 'tags',
},
},
{
name: 'categories',
title: 'Categories',
type: 'array',
of: [
{
type: 'reference',
to: {type: 'category'},
},
],
},

],

preview: {
select: {
title: 'title',
media: 'defaultProductVariant.images[0]',
},
},
}
productVariant

export default {
  title: 'Product variant',
  name: 'productVariant',
  type: 'object',
  fields: [
    {
      title: 'Title',
      name: 'title',
      type: 'string',
    },
    {
      title: 'Price',
      name: 'price',
      type: 'number',
    },
    {
      title: 'SKU',
      name: 'sku',
      type: 'string',
    },
    {
      name: 'images',
      title: 'Images',
      type: 'array',
      of: [
        {
          type: 'image',
          options: {
            hotspot: true,
          },
        },
      ],
    },
  ],
}
category

export default {
  name: 'category',
  title: 'Category',
  type: 'document',
  fields: [
    {
      name: 'title',
      title: 'Title',
      type: 'string',
    },
    {
      name: 'slug',
      title: 'Slug',
      type: 'slug',
      options: {
        source: 'title',
        maxLength: 96,
      },
    },
    {
      name: 'description',
      title: 'Description',
      type: 'text',
    },
    {
      name: 'parents',
      title: 'Parent categories',
      type: 'array',
      of: [
        {
          type: 'reference',
          to: [{type: 'category'}],
        },
      ],
    },
  ],
}
You'll want to change the query here to get categories instead of products. You'll then need to create another page for displaying products by category.
That is so helpful, thank you!!!
Would you mind giving me an example of how the new page might look? I can figure out the jsx, im just more confused about the static props and importing scripts etc
It's going to be quite similar to the existing product page , but again you'll want to replace any 'product' specific things with your 'category'. Just spitballing, but this can get you started:
import React, { useState } from 'react';

import { client, urlFor } from '../../lib/client';
import { useStateContext } from '../../context/StateContext';

const CategoryDetails = ({ category, allCategories }) => {
  //your component
}

export const getStaticPaths = async () => {
  const query = `*[_type == "category"] {
    slug {
      current
    }
  }
  `;

  const categories = await client.fetch(query);

  const paths = categories.map((category) => ({
    params: { 
      slug: category.slug.current
    }
  }));

  return {
    paths,
    fallback: 'blocking'
  }
}

export const getStaticProps = async ({ params: { slug }}) => {
  const query = `{
    "category": *[_type == "category" && slug.current == '${slug}'][0]{
       ...,
       parents[]->
     },
    "allCategories": *[_type == "category"]{
       ...,
       parents[]->
     },
  }`
  
  const data = await client.fetch(query);

  return {
    props: data
  }
}

export default ProductDetails
Beautiful. So where would the jsx go? Inside where you put //your component?
Also, does this code ensure that when they click a product on the product categories page it will bring them to the correct product? Or is there something else I need to add for this?
And will this page hold my carts state data? So like if they add something to cart, then press home and go to a different category, all their cart info will still be there?
I just don't want to break any functionality by trying to implement a new one lol I'm sorry I'm really new to this tech
I haven't added any of that functionality to this page. You'll have to play around, look at the product example, and build that out. I have showed you how to get the data out of Sanity though!
Got it. I'll figure it out. I really appreciate all your help. I have another thread open if you know anything about this as well, I just need a point in the right direction. https://sanity-io-land.slack.com/archives/C9Z7RC3V1/p1657209045403439?thread_ts=1657209045.403439&amp;cid=C9Z7RC3V1
Sure thing! I'll take a look at it today!
I would add the jsx where you left a note in the code above at //your component ?
Yes! Your JSX and any other functionality you need on your frontend would go there.
You've saved me hours of struggling through some of this, I really appreciate it
Happy to help get you pointed in the right direction!

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Was this answer helpful?