nuqs
@contract-kit/nuqs connects contract query schemas to URL-backed state. Use it for search, filters, tabs, sorting, and pagination when the URL should reflect the current view.
bun add @contract-kit/nuqs nuqs
Setup
import { createNQ } from "@contract-kit/nuqs";
export const nq = createNQ();
In Next.js App Router, mount the NuqsAdapter once:
import { NuqsAdapter } from "@contract-kit/nuqs/next/app";
export function Providers({ children }: { children: React.ReactNode }) {
return <NuqsAdapter>{children}</NuqsAdapter>;
}
URL-backed filters
import { useQuery } from "@tanstack/react-query";
import { parseAsString, parseAsStringLiteral } from "nuqs";
import { listContacts } from "@/app/contracts/contacts";
import { nq } from "@/app/lib/nq";
import { rq } from "@/app/lib/rq";
const contactsSearch = nq(listContacts).query({
parsers: {
search: parseAsString,
group: parseAsStringLiteral(["personal", "work", "family", "other"]),
},
history: "replace",
});
function ContactsPage() {
const [filters, setFilters] = contactsSearch.useState();
const query = useQuery(
contactsSearch.toQueryOptions(rq(listContacts), filters, {
query: { limit: 50, offset: 0 },
}),
);
return null;
}
toQueryOptions(...) composes with @contract-kit/react-query, so URL state and query input stay aligned with the same contract.