How to pass dynamic category ID to GROQ query in getStaticProps with slug param

9 replies
Last updated: Jan 28, 2026
hey all, Im stuck on this issue please help me to solve this. So I have multiple categories in Blogs. So I want to print Blogs related to the category. On the
category
page But It's not working. and how I add two params in getStaticProps. the slug param is working good but How do I add $Id. Blogs are filtering via Id but how add it on the frontend. On
GROQ
Playground all the blogs are filtering fine. But I'm stuck how I filter on frontend. How I pass $id

/**
 * Makes Next.js aware of all the slugs it can expect at this route
 */
export async function getStaticPaths() {
  const allCategoryQuery = groq`
  *[_type == "blogCategory" && defined(slug.current)][].slug.current
`;

  const pages = await getClient().fetch(allCategoryQuery);
  return {
    paths: pages.map((slug) => `/blog/category/${slug}`),
    fallback: true,
  };
}

export async function getStaticProps({ params, preview = false }) {
  const query = groq`
  *[_type == "blogCategory" && slug.current == $slug]{
    _id,title,description,slug,image,
     "blogCategory": *[_type=='article' &&  blogCategories[]._ref == "1b65f627-7d10-4a35-9062-41e9245ff272"]
   }
  `;

  const queryParams = { slug: params.slug };
  console.log(queryParams, 'queryParams');

  const data = await getClient(preview).fetch(query, queryParams);

  // Helper function to reduce all returned documents down to just one
  const page = filterDataToSingleItem(data, preview);
  return {
    props: {
      preview,
      siteSettings: await fetchGlobalSettings(),
      allCategories: await sanity.fetch(groq`
      *[_type == "blogCategory" && !(_id in path('drafts.**'))] {...}`),
      data: {
        page,
        query,
        queryParams,
      },
    },
  };
}
Currently Id is 1b65f627-7d10-4a35-9062-41e9245ff272 and Localhost Url is
http://localhost:3000/blog/category/first-category .
const BlogView = (props) => {
  const { data, preview, siteSettings = {}, allCategories } = props;

  const { data: previewData } = usePreviewSubscription(data?.query, {
    params: data?.queryParams ?? {},
    initialData: data?.page,
    enabled: preview,
  });
  // Client-side uses the same query, so we may need to filter it down again
  const page = filterDataToSingleItem(previewData, preview);
  if (!page) return null;

  return (
    <Layout siteSettingData={siteSettings}>
      {page &&
        page.blogCategory.map((item, i) => (
          <BlogItem data={item} key={item._id || i} />
        ))}
    </Layout>
  );
};

export default BlogView;
AI Update

I can see your issue! You're hardcoding the category ID ("1b65f627-7d10-4a35-9062-41e9245ff272") in your GROQ query instead of using the dynamic _id from the fetched category. Here's how to fix it:

The Problem: Your query fetches the category data first, but then uses a hardcoded ID in the subquery instead of referencing the current category's _id.

The Solution: Use the ^ parent operator in GROQ to reference the parent document's _id in your subquery:

export async function getStaticProps({ params, preview = false }) {
  const query = groq`
  *[_type == "blogCategory" && slug.current == $slug][0]{
    _id,
    title,
    description,
    slug,
    image,
    "blogCategory": *[_type=='article' && references(^._id)]
  }
  `;

  const queryParams = { slug: params.slug };
  const data = await getClient(preview).fetch(query, queryParams);

  return {
    props: {
      preview,
      siteSettings: await fetchGlobalSettings(),
      allCategories: await sanity.fetch(groq`
        *[_type == "blogCategory" && !(_id in path('drafts.**'))] {...}
      `),
      data: {
        page: data, // data is already a single item now
        query,
        queryParams,
      },
    },
  };
}

Key changes:

  1. Added [0] after the first filter to get a single category document instead of an array (so you don't need filterDataToSingleItem)

  2. Used references(^._id) instead of hardcoding the ID. The ^ operator references the parent document (the category), and references() checks if the article's blogCategories array contains a reference to that category's _id

  3. Simplified the data structure since we're now returning a single object instead of an array

Alternative approach if you prefer using query parameters:

const query = groq`
  *[_type == "blogCategory" && slug.current == $slug][0]{
    _id,
    title,
    description,
    slug,
    image,
    "blogCategory": *[_type=='article' && references($categoryId)]
  }
`;

const category = await getClient(preview).fetch(
  groq`*[_type == "blogCategory" && slug.current == $slug][0]._id`,
  { slug: params.slug }
);

const queryParams = { slug: params.slug, categoryId: category };
const data = await getClient(preview).fetch(query, queryParams);

However, the first approach with ^._id is cleaner and requires only one query.

Update your component to handle the simplified data structure:

const BlogView = (props) => {
  const { data, preview, siteSettings = {}, allCategories } = props;

  const { data: previewData } = usePreviewSubscription(data?.query, {
    params: data?.queryParams ?? {},
    initialData: data?.page,
    enabled: preview,
  });

  const page = previewData;
  
  if (!page || !page.blogCategory) return null;

  return (
    <Layout siteSettingData={siteSettings}>
      {page.blogCategory.map((item, i) => (
        <BlogItem data={item} key={item._id || i} />
      ))}
    </Layout>
  );
};

The references() function is the proper way to check if a document has a reference to another document in an array field. This will dynamically filter articles based on whichever category page you're viewing!

Show original thread
9 replies

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?