Type-safe search params state manager for Next.js - Like React.useState, but stored in the URL query string
Overall
score
96%
Utilities for generating query strings from typed values using parser configuration, enabling programmatic URL generation with the same type safety as query state hooks.
Creates a serializer function that generates query strings from typed values using the same parsers used by query state hooks.
/**
* Creates a serializer function for generating query strings
* @param parsers - Object mapping keys to their respective parsers with optional defaults
* @returns Serializer function with multiple overloads
*/
function createSerializer<Parsers extends Record<string, ParserWithOptionalDefault<any>>>(
parsers: Parsers
): SerializerFunction<Parsers>;
type ParserWithOptionalDefault<T> = ParserBuilder<T> & { defaultValue?: T };
interface SerializerFunction<Parsers extends Record<string, ParserWithOptionalDefault<any>>> {
/** Generate a query string for the given values */
(values: Values<Parsers>): string;
/** Append/amend the query string of the given base with the given values */
(base: Base, values: Values<Parsers> | null): string;
}
type Base = string | URLSearchParams | URL;
type Values<Parsers extends Record<string, ParserWithOptionalDefault<any>>> = Partial<{
[K in keyof Parsers]?: ExtractParserType<Parsers[K]>;
}>;
type ExtractParserType<Parser> = Parser extends ParserBuilder<any>
? ReturnType<Parser['parseServerSide']>
: never;Usage Examples:
import { createSerializer, parseAsString, parseAsInteger, parseAsBoolean } from "nuqs";
// Define the serializer with the same parsers used in components
const searchSerializer = createSerializer({
q: parseAsString,
page: parseAsInteger.withDefault(1),
category: parseAsString,
featured: parseAsBoolean.withDefault(false)
});
// Generate query strings
const queryString1 = searchSerializer({
q: 'react',
page: 2,
category: 'libraries'
});
// Result: "?q=react&page=2&category=libraries"
const queryString2 = searchSerializer({
q: 'nextjs',
featured: true
});
// Result: "?q=nextjs&featured=true"
// Note: page is omitted because it equals the default value
// Clear all parameters
const emptyQuery = searchSerializer({});
// Result: ""Append or modify existing URLs with new query parameters.
/**
* Append/amend the query string of the given base with the given values
* Existing search param values will be kept, unless:
* - the value is null, in which case the search param will be deleted
* - another value is given for an existing key, in which case the search param will be updated
*/
(base: Base, values: Partial<ExtractParserTypes<Parsers>> | null): string;Usage Examples:
// Starting with a base URL
const baseUrl = "/search?existing=value&other=param";
// Add/modify specific parameters
const newUrl1 = searchSerializer(baseUrl, {
q: 'typescript',
page: 3
});
// Result: "/search?existing=value&other=param&q=typescript&page=3"
// Clear specific parameters with null
const newUrl2 = searchSerializer(baseUrl, {
q: null,
category: 'tools'
});
// Result: "/search?existing=value&other=param&category=tools"
// Clear all managed parameters
const clearedUrl = searchSerializer(baseUrl, null);
// Result: "/search?existing=value&other=param"
// Only removes keys defined in the parser schema
// Working with URLSearchParams
const params = new URLSearchParams("?existing=value");
const urlWithParams = searchSerializer(params, {
q: 'search-term',
page: 1
});
// Result: "?existing=value&q=search-term&page=1"
// Working with URL objects
const url = new URL("https://example.com/search?existing=value");
const fullUrl = searchSerializer(url, {
q: 'react'
});
// Result: "https://example.com/search?existing=value&q=react"Works with all parser types including arrays, enums, and custom parsers.
import {
createSerializer,
parseAsArrayOf,
parseAsStringEnum,
parseAsJson,
parseAsIsoDateTime
} from "nuqs";
// Complex serializer with various data types
const advancedSerializer = createSerializer({
tags: parseAsArrayOf(parseAsString),
status: parseAsStringEnum(['active', 'inactive'] as const).withDefault('active'),
config: parseAsJson<{ theme: string; notifications: boolean }>(),
createdAfter: parseAsIsoDateTime
});
// Serialize complex data
const complexQuery = advancedSerializer({
tags: ['react', 'nextjs', 'typescript'],
status: 'active',
config: { theme: 'dark', notifications: true },
createdAfter: new Date('2023-01-01')
});
// Result: "?tags=react,nextjs,typescript&config=%7B%22theme%22:%22dark%22,%22notifications%22:true%7D&createdAfter=2023-01-01T00:00:00.000Z"
// Note: status omitted because it equals default value, config is JSON-encoded and URI-encodedCommon pattern for generating links with query parameters in Next.js applications.
import Link from "next/link";
// Search results component
function SearchResults({ currentQuery, currentPage, totalPages }) {
// Generate pagination links
const nextPageUrl = searchSerializer('/search', {
q: currentQuery,
page: currentPage + 1
});
const prevPageUrl = searchSerializer('/search', {
q: currentQuery,
page: Math.max(1, currentPage - 1)
});
// Generate filter links
const categoryLinks = ['tools', 'libraries', 'frameworks'].map(category => ({
category,
url: searchSerializer('/search', {
q: currentQuery,
category,
page: 1 // Reset to first page when changing category
})
}));
return (
<div>
{/* Pagination */}
{currentPage > 1 && (
<Link href={prevPageUrl}>Previous</Link>
)}
{currentPage < totalPages && (
<Link href={nextPageUrl}>Next</Link>
)}
{/* Category filters */}
{categoryLinks.map(({ category, url }) => (
<Link key={category} href={url}>
{category}
</Link>
))}
</div>
);
}The serializer respects default values and clearOnDefault settings from parsers.
const parserWithDefaults = createSerializer({
page: parseAsInteger.withDefault(1),
sort: parseAsString.withDefault('name'),
enabled: parseAsBoolean.withDefault(false).withOptions({ clearOnDefault: true })
});
// Default values are omitted from URL
const url1 = parserWithDefaults({
page: 1, // Omitted (equals default)
sort: 'name', // Omitted (equals default)
enabled: false // Omitted (equals default + clearOnDefault: true)
});
// Result: ""
// Non-default values are included
const url2 = parserWithDefaults({
page: 2,
sort: 'date',
enabled: true
});
// Result: "?page=2&sort=date&enabled=true"Use with Next.js router for programmatic navigation.
import { useRouter } from "next/navigation";
function SearchForm() {
const router = useRouter();
const handleSearch = (formData: FormData) => {
const searchParams = {
q: formData.get('query') as string,
category: formData.get('category') as string,
page: 1 // Reset to first page
};
const url = searchSerializer('/search', searchParams);
router.push(url);
};
return (
<form action={handleSearch}>
<input name="query" placeholder="Search..." />
<select name="category">
<option value="">All Categories</option>
<option value="tools">Tools</option>
<option value="libraries">Libraries</option>
</select>
<button type="submit">Search</button>
</form>
);
}Install with Tessl CLI
npx tessl i tessl/npm-nuqsevals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10