Slow response time for queries in Sanity.io project

5 replies
Last updated: Jun 22, 2023
export async function getFilteredVideos( limit,
filterGrade,
filterCategory,
search,
sort,
type = "Lesson Video",
subCategory
) {
console.time("videos fetch");
let query = `*[_type == "video"]{${videoFields}}`;

query += `[type.name == "${type}"]`;

if (filterGrade && filterGrade !== "All Grades") {
query += `[ "${filterGrade}" in grade[].label ]`;
}

if (filterCategory && filterCategory !== "All Categories") {
query += `[ "${filterCategory}" in category[].title ]`;
}

if (subCategory && subCategory !== "All Subtopic") {
query += `[subCategory.title match "${subCategory}"]`;
}

if (search && search !== "All") {
query += `[name match "${search}*"]`;
}

if (sort && sort === "longest") {
query += `| order(duration desc)`;
}

if (sort && sort === "shortest") {
query += `| order(duration asc)`;
}

if (sort && sort === "relevance") {
query += `| order(type.name desc)`;
}

query += `${limit}`;

const results = await client.fetch(query);
console.timeEnd("videos fetch");
return results;
}

export const getAllSubCategories = async (topic, grade) => {
const fields = `title,category->{title,'grade': grade->label}`;
const results = await client.fetch(

*[_type == 'videoSubCategory']{${fields}}[category.title == "${topic}"] | order(title asc)
);
const filteredForGrade = results.filter((subtopic) => {
return subtopic.category.grade === grade;
});
return filteredForGrade;
};

export async function getAllSubCatgoriesVideos(topic, grade) {
console.time("subcategory");
const fields = `title,category->{title,'grade': grade->label}`;
const results = await client.fetch(

*[_type == 'videoSubCategory']{${fields}}[category.title == "${topic}"] | order(title asc)
);
const filteredForGrade = results.filter((subtopic) => {
return subtopic.category.grade === grade;
});

const subCategoriesWithVideos = await Promise.all(
filteredForGrade.map(async (subcategory) => {
const videos = await getFilteredVideos(

[0..3]
, grade,
topic,
false,
false,
"Concept Video",
subcategory.title
);
return { ...subcategory, videos };
})
);
console.timeEnd("subcategory");
return subCategoriesWithVideos;
}

export async function getFilteredVideosCount(
filterGrade,
filterCategory,
search,
type = "Lesson Video",
subCategory
) {
console.time("count");
let query = `*[_type == "video"]{${videoFields}}`;

query += `[type.name == "${type}"]`;

if (filterGrade && filterGrade !== "All Grades") {
query += `[ "${filterGrade}" in grade[].label ]`;
}

if (filterCategory && filterCategory !== "All Categories") {
query += `[ "${filterCategory}" in category[].title ]`;
}

if (subCategory && subCategory !== "All Subtopic") {
query += `[subCategory.title match "${subCategory}"]`;
}

if (search && search !== "All") {
query += `[name match "${search}*"]`;
}

const results = await client.fetch(
count(${query})
); console.timeEnd("count");
return results;
}
these are some of my queries i believe the data i am fetching is not that large how much time should it take for the query to fetch data it is taking 4-8seconds for each req and acc to me it is too much for this small amount of data have i wrote queries wrong what can be the reasons of response being this slow?
AI Update

Looking at your queries, there are several performance issues that could explain the 4-8 second response times. Let me walk through the problems and solutions:

Real Performance Issues

1. N+1 Query Problem in getAllSubCatgoriesVideos

This is your biggest performance killer:

const subCategoriesWithVideos = await Promise.all(
  filteredForGrade.map(async (subcategory) => {
    const videos = await getFilteredVideos(...); // Separate query for EACH subcategory!
    return { ...subcategory, videos };
  })
);

If you have 10 subcategories, you're making 11 separate queries (1 for subcategories + 10 for videos). This compounds the latency. Instead, fetch everything in one query using GROQ projections:

export async function getAllSubCatgoriesVideos(topic, grade) {
  const results = await client.fetch(
    `*[_type == 'videoSubCategory' 
      && category->title == $topic 
      && category->grade->label == $grade] {
      title,
      category->{title, 'grade': grade->label},
      'videos': *[_type == 'video' 
        && type->name == "Concept Video"
        && references(^._id)
        && $grade in grade[]->label
        && $topic in category[]->title][0...3]{${videoFields}}
    } | order(title asc)`,
    { topic, grade }
  );
  return results;
}

2. Client-Side Filtering Instead of GROQ

In getAllSubCategories, you're fetching all subcategories then filtering in JavaScript:

const filteredForGrade = results.filter((subtopic) => {
  return subtopic.category.grade === grade;
});

This forces Sanity to return unnecessary data. Move the filter into your GROQ query as shown in the example above with category->grade->label == $grade.

3. Not Using Query Parameters

Your string concatenation approach has security and caching implications. According to the GROQ parameters documentation, using parameters prevents injection issues and allows better query caching:

// Current approach - works but not ideal
query += `[category.title == "${topic}"]`;

// Better approach with parameters
const results = await client.fetch(
  `*[_type == "video" && $topic in category[]->title]{...}`,
  { topic: filterCategory }
);

Note: Your current filter chaining syntax (like *[_type == "video"]{...}[type.name == "..."]) is actually valid GROQ according to the pipeline components documentation - filters can be chained after projections. However, it's generally more efficient to combine filters before the projection using &&.

Here's a more performant version of getFilteredVideos:

export async function getFilteredVideos(
  limit,
  filterGrade,
  filterCategory,
  search,
  sort,
  type = "Lesson Video",
  subCategory
) {
  const filters = ['_type == "video"', 'type->name == $type'];
  const params = { type };
  
  if (filterGrade && filterGrade !== "All Grades") {
    filters.push('$grade in grade[]->label');
    params.grade = filterGrade;
  }
  if (filterCategory && filterCategory !== "All Categories") {
    filters.push('$category in category[]->title');
    params.category = filterCategory;
  }
  if (subCategory && subCategory !== "All Subtopic") {
    filters.push('subCategory->title match $subCategory');
    params.subCategory = subCategory;
  }
  if (search && search !== "All") {
    filters.push('name match $search');
    params.search = `${search}*`;
  }
  
  let query = `*[${filters.join(' && ')}]{${videoFields}}`;
  
  if (sort === "longest") query += ' | order(duration desc)';
  if (sort === "shortest") query += ' | order(duration asc)';
  if (sort === "relevance") query += ' | order(type->name desc)';
  
  query += ` [0...${limit}]`;
  
  const results = await client.fetch(query, params);
  return results;
}

Additional Performance Checks

Based on the query optimization guide:

  1. Check your dataset size: 4-8 seconds suggests either a very large dataset or other issues. How many video documents do you have?

  2. Enable API CDN: Make sure you're using the API CDN which caches responses. Use useCdn: true in your client config:

const client = createClient({
  // ... other config
  useCdn: true,
});
  1. Use the published perspective: If you don't need draft content, the published perspective is faster than raw.

  2. Test in Vision plugin: Use the Vision plugin in your Studio to test query performance directly and see execution times.

With these changes—especially fixing the N+1 problem—you should see dramatic improvements. The single biggest win will be consolidating those multiple queries into one.

yes
Well, I don't know how GROQ works internally, but usually
match/regex
type queries would be slower and also
order
. Try to remove the
order
and do it through code.
no change in req time after removing order
This guide will help you fix the issues with your query.

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?