Optimizing a query function to fetch filtered videos
Looking at your query, there are several performance issues that are likely causing slowness. Here's how to make it significantly faster:
Main Issues
1. String concatenation is building an invalid GROQ query
Your current approach concatenates filter conditions incorrectly. Each filter adds another array slice [condition], which doesn't create valid GROQ syntax. You need proper boolean logic with && operators.
2. No indexes are being utilized
The query starts with *[_type == "video"] which is good, but subsequent filters aren't optimized for the query engine.
3. Multiple reference dereferencing
Fields like grade[].label and category[].title suggest you're dereferencing arrays of references, which is expensive.
Optimized Solution
export async function getFilteredVideos(
limit,
filterGrade,
filterCategory,
search,
sort,
type = "Lesson Video",
subCategory
) {
console.time("videos fetch");
// Build filter conditions as an array
const conditions = ['_type == "video"', `type.name == $type`];
if (filterGrade && filterGrade !== "All Grades") {
conditions.push(`$filterGrade in grade[].label`);
}
if (filterCategory && filterCategory !== "All Categories") {
conditions.push(`$filterCategory in category[].title`);
}
if (subCategory && subCategory !== "All Subtopic") {
conditions.push(`subCategory.title match $subCategory`);
}
if (search && search !== "All") {
conditions.push(`name match $search`);
}
// Combine all conditions with &&
const filterClause = conditions.join(' && ');
// Build order clause
let orderClause = '';
if (sort === "longest") orderClause = '| order(duration desc)';
else if (sort === "shortest") orderClause = '| order(duration asc)';
else if (sort === "relevance") orderClause = '| order(type.name desc)';
// Build final query with parameters
const query = `*[${filterClause}] ${orderClause} [0...${limit}] {${videoFields}}`;
const results = await client.fetch(query, {
type,
filterGrade,
filterCategory,
subCategory,
search: search ? `${search}*` : undefined
});
console.timeEnd("videos fetch");
return results;
}Key Improvements
1. Use query parameters: Passing values as parameters (the second argument to client.fetch()) is more secure and allows the query engine to optimize better.
2. Proper filter syntax: Combining conditions with && creates a valid GROQ filter that the query engine can process efficiently.
3. Move projection to the end: According to GROQ query optimization best practices, you should filter and sort first, then project fields. This reduces the amount of data processed.
4. Optimize array slicing: Use [0...${limit}] instead of appending the limit - this is the correct GROQ syntax for slicing.
Additional Optimization Tips
If this is still slow with large datasets:
- Add indexes to frequently filtered fields like
type.nameandgrade[].label(if you're on a Growth plan or higher) - Reduce reference dereferencing: If
grade[].labelandcategory[].titleare references being dereferenced, consider denormalizing this data or using a more efficient data structure - Limit your projection: Only include fields in
videoFieldsthat you actually need - Consider pagination: If you're showing paginated results, implement cursor-based pagination which is more efficient than offset-based pagination for large datasets
The query parameter approach combined with proper filter syntax should give you a significant performance boost immediately.
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.