Overview
The createSerializer function creates a type-safe serializer for generating URLs with search parameters. It’s useful for creating links, redirects, and programmatic navigation with properly formatted query strings.
Basic Usage
import { createSerializer , parseAsString , parseAsInteger } from 'nuqs/server'
const serialize = createSerializer ({
q: parseAsString ,
page: parseAsInteger ,
category: parseAsString
})
// Generate a query string
const url = serialize ({ q: 'laptop' , page: 2 , category: 'electronics' })
// "?q=laptop&page=2&category=electronics"
Function Signature
function createSerializer < Parsers extends ParserMap >(
parsers : Parsers ,
options ?: CreateSerializerOptions < Parsers >
) : SerializeFunction < Parsers >
Parameters
An object mapping search param keys to their parser configurations.
Optional configuration object. When true, values matching their parser’s default value will be omitted from the URL. const serialize = createSerializer (
{ page: parseAsInteger . withDefault ( 1 ) },
{ clearOnDefault: true }
)
serialize ({ page: 1 }) // "" (omitted because it's the default)
serialize ({ page: 2 }) // "?page=2"
Map internal state keys to different URL query parameter names. const serialize = createSerializer (
{ query: parseAsString },
{ urlKeys: { query: 'q' } }
)
serialize ({ query: 'laptop' }) // "?q=laptop"
processUrlSearchParams
(searchParams: URLSearchParams) => URLSearchParams
A function to post-process the URLSearchParams before serialization. Useful for adding custom logic or additional parameters. const serialize = createSerializer (
{ q: parseAsString },
{
processUrlSearchParams : ( params ) => {
params . set ( 'utm_source' , 'app' )
return params
}
}
)
Serialize Function
The returned serializer function has two overloads:
Generate Query String
serialize ( values : Partial < Nullable < ParsedValues >> ): string
Generates a query string from the provided values:
const serialize = createSerializer ({
q: parseAsString ,
page: parseAsInteger ,
category: parseAsString
})
serialize ({ q: 'laptop' , page: 2 })
// "?q=laptop&page=2"
serialize ({ category: 'electronics' })
// "?category=electronics"
Append to Base URL
serialize (
base : string | URL | URLSearchParams ,
values : Partial < Nullable < ParsedValues >> | null
): string
Appends or amends the query string of a base URL:
const serialize = createSerializer ({
q: parseAsString ,
page: parseAsInteger
})
// Append to a path
serialize ( '/search' , { q: 'laptop' , page: 2 })
// "/search?q=laptop&page=2"
// Amend existing query string
serialize ( '/search?sort=price' , { q: 'laptop' })
// "/search?sort=price&q=laptop"
// Use with URL object
const url = new URL ( 'https://example.com/search' )
serialize ( url , { q: 'laptop' })
// "https://example.com/search?q=laptop"
// Use with URLSearchParams
const params = new URLSearchParams ( '?sort=price' )
serialize ( params , { q: 'laptop' })
// "?sort=price&q=laptop"
Null Values
Passing null for a value removes it from the URL:
const serialize = createSerializer ({
q: parseAsString ,
page: parseAsInteger ,
category: parseAsString
})
// Remove specific parameters
serialize ( '/search?q=laptop&page=2&category=electronics' , {
category: null // Remove category
})
// "/search?q=laptop&page=2"
// Remove all parameters by passing null as second argument
serialize ( '/search?q=laptop&page=2' , null )
// "/search"
Usage with Link Components
Next.js
import Link from 'next/link'
import { createSerializer , parseAsString , parseAsInteger } from 'nuqs/server'
const serialize = createSerializer ({
q: parseAsString ,
page: parseAsInteger ,
category: parseAsString
})
export function ProductLink ({ category } : { category : string }) {
return (
< Link href = { serialize ( '/products' , { category , page: 1 }) } >
View { category }
</ Link >
)
}
export function PaginationLink ({ page } : { page : number }) {
return (
< Link href = { serialize ({ page }) } >
Page { page }
</ Link >
)
}
Remix
import { Link } from '@remix-run/react'
import { createSerializer , parseAsString } from 'nuqs/server'
const serialize = createSerializer ({
filter: parseAsString ,
sort: parseAsString
})
export function FilterLink ({ filter } : { filter : string }) {
return (
< Link to = { serialize ({ filter }) } >
{ filter }
</ Link >
)
}
React Router
import { Link } from 'react-router-dom'
import { createSerializer , parseAsString } from 'nuqs/server'
const serialize = createSerializer ({
tab: parseAsString . withDefault ( 'overview' )
})
export function TabLink ({ tab } : { tab : string }) {
return (
< Link to = { serialize ({ tab }) } >
{ tab }
</ Link >
)
}
Programmatic Navigation
Server-Side Redirects (Next.js)
import { redirect } from 'next/navigation'
import { createSerializer , parseAsString } from 'nuqs/server'
const serialize = createSerializer ({
q: parseAsString
})
export async function searchAction ( formData : FormData ) {
'use server'
const query = formData . get ( 'q' ) as string
// Redirect to search results
redirect ( serialize ( '/search' , { q: query }))
}
API Routes
import { createSerializer , parseAsString , parseAsInteger } from 'nuqs/server'
const serialize = createSerializer ({
page: parseAsInteger ,
limit: parseAsInteger
})
export async function GET ( request : Request ) {
const nextPageUrl = serialize (
new URL ( request . url ),
{ page: 2 , limit: 20 }
)
return Response . json ({
data: [ ... ],
nextPage: nextPageUrl
})
}
Default Value Behavior
By default, values matching their parser’s default are omitted from the URL:
const serialize = createSerializer ({
page: parseAsInteger . withDefault ( 1 ),
limit: parseAsInteger . withDefault ( 20 )
})
serialize ({ page: 1 , limit: 20 })
// "" (both values are defaults, so omitted)
serialize ({ page: 2 , limit: 20 })
// "?page=2" (limit omitted because it's the default)
serialize ({ page: 1 , limit: 50 })
// "?limit=50" (page omitted because it's the default)
Disable this behavior with clearOnDefault: false:
const serialize = createSerializer (
{
page: parseAsInteger . withDefault ( 1 )
},
{ clearOnDefault: false }
)
serialize ({ page: 1 })
// "?page=1" (included even though it's the default)
URL Keys Mapping
Map internal keys to different URL parameter names:
const serialize = createSerializer (
{
searchQuery: parseAsString ,
pageNumber: parseAsInteger ,
itemsPerPage: parseAsInteger
},
{
urlKeys: {
searchQuery: 'q' ,
pageNumber: 'page' ,
itemsPerPage: 'limit'
}
}
)
serialize ({
searchQuery: 'laptop' ,
pageNumber: 2 ,
itemsPerPage: 50
})
// "?q=laptop&page=2&limit=50"
Complex Serialization Example
import {
createSerializer ,
parseAsString ,
parseAsInteger ,
parseAsArrayOf ,
parseAsIsoDateTime ,
parseAsStringLiteral
} from 'nuqs/server'
const serialize = createSerializer (
{
search: parseAsString ,
page: parseAsInteger . withDefault ( 1 ),
limit: parseAsInteger . withDefault ( 20 ),
tags: parseAsArrayOf ( parseAsString ),
from: parseAsIsoDateTime ,
to: parseAsIsoDateTime ,
sortBy: parseAsStringLiteral ([ 'asc' , 'desc' ] as const )
},
{
urlKeys: {
search: 'q' ,
limit: 'per_page'
}
}
)
serialize ({
search: 'laptop' ,
page: 2 ,
tags: [ 'electronics' , 'sale' ],
from: new Date ( '2024-01-01' ),
sortBy: 'desc'
})
// "?q=laptop&page=2&tags=electronics&tags=sale&from=2024-01-01T00:00:00.000Z&sortBy=desc"
Sharing with Hooks and Cache
Share the same parser configuration across serializers, loaders, caches, and hooks:
// shared/searchParams.ts
import { parseAsString , parseAsInteger } from 'nuqs/server'
export const searchParsers = {
q: parseAsString . withDefault ( '' ),
page: parseAsInteger . withDefault ( 1 ),
category: parseAsString
}
export const searchUrlKeys = {
q: 'q' ,
page: 'p' ,
category: 'cat'
}
// Server: serializer
import { createSerializer } from 'nuqs/server'
import { searchParsers , searchUrlKeys } from './shared/searchParams'
export const serializeSearch = createSerializer ( searchParsers , {
urlKeys: searchUrlKeys
})
// Server: cache
import { createSearchParamsCache } from 'nuqs/server'
import { searchParsers , searchUrlKeys } from './shared/searchParams'
export const searchParamsCache = createSearchParamsCache ( searchParsers , {
urlKeys: searchUrlKeys
})
// Client: hooks
import { useQueryStates } from 'nuqs'
import { searchParsers , searchUrlKeys } from './shared/searchParams'
export function SearchFilters () {
const [ filters , setFilters ] = useQueryStates ( searchParsers , {
urlKeys: searchUrlKeys
})
// ...
}
Type Inference
The serializer is fully type-safe:
const serialize = createSerializer ({
count: parseAsInteger ,
active: parseAsBoolean ,
tags: parseAsArrayOf ( parseAsString )
})
// TypeScript knows the shape of the values object
serialize ({
count: 42 , // number | null
active: true , // boolean | null
tags: [ 'a' , 'b' ] // string[] | null
})
// ❌ TypeScript error: wrong type
serialize ({ count: 'not a number' })
// ❌ TypeScript error: unknown key
serialize ({ unknown: 'value' })
Post-Processing
Use processUrlSearchParams to add custom logic:
const serialize = createSerializer (
{
q: parseAsString ,
page: parseAsInteger
},
{
processUrlSearchParams : ( params ) => {
// Add analytics parameters
params . set ( 'utm_source' , 'app' )
params . set ( 'utm_medium' , 'link' )
// Sort parameters alphabetically
params . sort ()
return params
}
}
)
serialize ({ q: 'laptop' , page: 2 })
// "?page=2&q=laptop&utm_medium=link&utm_source=app"
Best Practices
Share parser configurations
Define parsers once and reuse them in serializers, loaders, caches, and hooks for consistency.
Use URL keys for clean URLs
Map verbose internal names to short URL parameter names: const serialize = createSerializer (
{ searchQuery: parseAsString },
{ urlKeys: { searchQuery: 'q' } }
)
Leverage clearOnDefault
Keep URLs clean by omitting default values (enabled by default).
Use null to remove parameters
Explicitly remove parameters by passing null as their value.
Loaders Parse search params with createLoader
Cache Access params in server components
Parsers Learn about available parser types