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

9 replies
Last updated: Jun 28, 2022
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
There are quite a few things to unpack here. First of all, your
getStaticPaths
does not return the right data. It should return an array of object with a
params
property, not actual paths as strings. Something like this:
[
  { params: { slug: 'category-1' } },
  { params: { slug: 'category-2' } },
  { params: { slug: 'category-3' } },
]
Regarding
getStaticProps
, I think you’re making your life too difficult. You receive a category slug from the parameter. All you want are all the posts whose category has this slug. Without knowing your schema, I would say something like this:
*[ _type == "article" && $slug in blogCategories[]->slug.current]
Then, I’m not sure you should be reducing all your data to a single entry. Ultimately you want a collection or blog posts for that category, don’t you?
Is there any way to add query params like this in getStaticProps? I tries this in getserverSide props its working fine for me. But In getstaticprops its throwing error call id is undefined
Well you receive the route params in the
params
object. So if your page is called
[slug].js
, you receive
{ params: { slug: 'foo' } }
.
Thank you for help all working ......

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

  return {
    paths: pages.map((slug) => `/blog/${slug}`),
    fallback: true,
  };
}

export async function getStaticProps({ params, preview = false }) {
  const query = groq`
  *[_type == "article" && slug.current == $slug]
  { ...,
  "id": _id,author->{avatar,name,description,twitter},
  "blogCategories":blogCategories[]->{title,image,slug,description,slug,
  ${fetchContentBlocks}
  }
}
`;
  const queryParams = { slug: params.slug };
  const data = await getClient(preview).fetch(query, queryParams);
  // console.log('data', data);

  // Escape hatch, if our query failed to return data
  if (!data) return { notFound: true };

  // Helper function to reduce all returned documents down to just one
  const page = filterDataToSingleItem(data, preview);

  return {
    props: {
      preview,
      data: {
        query,
        queryParams,
        page,
        siteSettings: await fetchGlobalSettings(),
      },
    },
  };
}

Thank you all working now .....

/**
 * 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]
  {...,
  "relatedBlogs":*[ _type == "article" && $slug in blogCategories[]->slug.current]}
`;

  const queryParams = { slug: params.slug };
  const data = await getClient(preview).fetch(query, queryParams);
  if (!data) return { notFound: true };

  // 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,
      },
    },
  };
}

Is there any way to speed up the blog page?Speed is slow minor slow but ok
can you please help in this??

Im using getstaticprops.
I guess it depends a lot what is the performance bottleneck. First, make sure you’re not assuming that just from your local environment, because it will always be significantly slower than production.
Ok thanks you.
yes its working very fast on prouction. thanks.

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?