Slow response time for queries in Sanity.io project
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 &&.
Recommended Rewrite
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:
Check your dataset size: 4-8 seconds suggests either a very large dataset or other issues. How many video documents do you have?
Enable API CDN: Make sure you're using the API CDN which caches responses. Use
useCdn: truein your client config:
const client = createClient({
// ... other config
useCdn: true,
});Use the
publishedperspective: If you don't need draft content, thepublishedperspective is faster thanraw.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.
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.