Take control of query parameters using TypeScript-powered utilities like queryExtractor, paramsExtractor, and fetchData. A real-world guide to scaling your dynamic pages in React & Next.js
📌 Overview
If you're building a modern frontend application with Next.js App Router (14/15), you’re likely dealing with query params for:
- Search
- Filter (price range, brand, category, color)
- Sort
- Pagination
This article will walk you through how to parse, build, and use query parameters using powerful, reusable TypeScript utilities like:
🔍 paramsExtractor
🔧 queryExtractor
⚙️ fetchData
🧠 Why This Approach?
✅ Next.js 14 compatible — works with App Router and server components
✅ Type-safe and reusable
✅ Easily composable with filters, pagination, and API queries
✅ Works seamlessly with React Query or server-side fetching
✅ Clean and maintainable for long-term scaling
🧩 Example: Category Page in Next.js
const CategoryPage = async ({ searchParams, }: { searchParams: Promise<Record<string, string | string[]> | undefined>; }) => { const { filter } = await paramsExtractor({ searchParam: searchParams, }); const stringifiedQuery = queryExtractor({ sortBy: filter?.sortBy, extra: { "variants.sellingPrice[gte]": filter?.min_price, "variants.sellingPrice[lte]": filter?.max_price, "variants.variantName": filter?.color_name ? filter.color_name.split(",").map((b) => b.trim()) : [], "brand.brandName": filter?.brandNames ? filter.brandNames.split(",").map((b) => b.trim()) : [], "category.categoryName": filter?.categoryNames ? filter.categoryNames.split(",").map((b) => b.trim()) : [], }, }); const productsData = await fetchData({ route: "/product", query: stringifiedQuery, limit: 18, tags: ["/product"], }); // render your component using productsData };
🧩 What Is paramsExtractor?
If you haven’t read it yet, check out the full breakdown of paramsExtractor. It's a utility that parses searchParams from Next.js and returns a normalized object with:
- Cleaned values
- Mapped keys
- Optional conversion to array, number, or string
- Proper defaults
Perfect for server-side usage in dynamic routes like /category?brand=Apple,Samsung&sortBy=price.
🔧 queryExtractor: Build Clean Query Strings
This utility transforms a QueryOptions object into a query string with support for:
- Defaults
- Nested filtering (e.g., variants.sellingPrice[gte])
- Arrays
- Optional values
⚡ fetchData: Data Fetching Made Clean
fetchData is an abstraction over fetch (or Axios) with a powerful config API:
await fetchData({ route: "/product", query: "category=Phone&brand=Apple", limit: 18, tags: ["/product"], });
More on this utility in the official guide 👉 read now
🧵 Connect Everything
With these 3 pieces, your Next.js dynamic page becomes scalable and maintainable:
- paramsExtractor: Server-side param parsing
- queryExtractor: Build structured queries
- fetchData: Fetch paginated, sorted, and filtered data from the backend
All of this while keeping TypeScript and React Query (or fetch) integration seamless.
🔧 Using queryExtractor as a Standalone Utility
📦 Step 1: Add the Utility Function
Here’s the complete code for queryExtractor:
export interface QueryOptions { searchTerm?: string; sortBy?: string; sortOrder?: "asc" | "desc"; page?: number; limit?: number; extra?: Record<string, any>; } export const queryExtractor = ({ searchTerm, sortBy = "created_at", sortOrder = "desc", page = 1, limit = 10, extra = {}, }: QueryOptions): string => { const query = new URLSearchParams(); if (searchTerm?.trim()) query.set("searchTerm", searchTerm.trim()); query.set("sortBy", sortBy); query.set("sortOrder", sortOrder); query.set("page", Math.max(1, page).toString()); query.set("limit", Math.max(1, limit).toString()); Object.entries(extra).forEach(([key, value]) => { if (value !== undefined && value !== null) { if (Array.isArray(value)) { value.forEach((val) => { if (val !== "") query.append(key, String(val)); }); } else { query.append(key, String(value)); } } }); return query.toString(); };
✅ Step 2: Basic Example
import { queryExtractor } from "./queryExtractor"; const queryString = queryExtractor({ searchTerm: "laptop", sortBy: "price", sortOrder: "asc", page: 2, limit: 20, extra: { "brand": ["HP", "Dell"], "category": "Electronics", "price[gte]": 1000, "price[lte]": 3000, }, }); console.log(queryString); // Output: // searchTerm=laptop&sortBy=price&sortOrder=asc&page=2&limit=20&brand=HP&brand=Dell&category=Electronics&price[gte]=1000&price[lte]=3000
💡Pro Tips
- Supports arrays (e.g. brand=HP&brand=Dell)
- Ignores empty/null/undefined values
- Safe defaults for sortBy, sortOrder, page, and limit
- Can be reused anywhere: backend services, Node.js CLI tools, etc.
🏁 Final Thoughts
This setup:
✅ Makes query management clean and DRY
✅ Enhances developer experience with strong typing
✅ Scales easily with complex filters
✅ Plays nicely with Next.js App Router
✅ Perfect for dynamic category/product/search pages
👨💻 About the Author
Name: JAKER HOSSAIN
Username: @jackfd120
Role: Senior Frontend Developer
Portfolio: https://www.poranfolio.space/
I write about scalable frontend architecture, modern React/Next.js patterns, and TypeScript best practices.
If you liked this post, feel free to follow or reach out via my portfolio!
Top comments (0)