Simplifying Query Parameters in Next.js 14/15 with React Query and TypeScript
JAKER HOSSAIN

JAKER HOSSAIN @jackfd120

About: Frontend Developer | Programming Enthusiast | Lifelong Learner Passionate frontend developer with 2 years of experience in React.js and Next.js. Always eager to learn and tackle new coding challenges.

Location:
Dhaka, Bangladesh
Joined:
Dec 19, 2024

Simplifying Query Parameters in Next.js 14/15 with React Query and TypeScript

Publish Date: Jun 19
1 0

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:

  1. Search
  2. Filter (price range, brand, category, color)
  3. Sort
  4. 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
};
Enter fullscreen mode Exit fullscreen mode

🧩 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:

  1. Cleaned values
  2. Mapped keys
  3. Optional conversion to array, number, or string
  4. 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:

  1. Defaults
  2. Nested filtering (e.g., variants.sellingPrice[gte])
  3. Arrays
  4. 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"],
});
Enter fullscreen mode Exit fullscreen mode

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:

  1. paramsExtractor: Server-side param parsing
  2. queryExtractor: Build structured queries
  3. 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();
};
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

💡Pro Tips

  1. Supports arrays (e.g. brand=HP&brand=Dell)
  2. Ignores empty/null/undefined values
  3. Safe defaults for sortBy, sortOrder, page, and limit
  4. 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!

Comments 0 total

    Add comment