diff --git a/README.md b/README.md index ea44e86f2..715b88d9b 100644 --- a/README.md +++ b/README.md @@ -654,12 +654,9 @@ to do so in a type-safe manner. ```tsx // searchParams.ts -import { - createSearchParamsCache, - parseAsInteger, - parseAsString -} from 'nuqs/server' -// Note: import from 'nuqs/server' to avoid the "use client" directive +import { createSearchParamsCache } from 'nuqs/server/cache' +import { parseAsInteger, parseAsString } from 'nuqs/server' +// Note: import parsers from 'nuqs/server' to avoid the "use client" directive export const searchParamsCache = createSearchParamsCache({ // List your search param keys and associated parsers here: @@ -701,7 +698,8 @@ parser declaration with `useQueryStates` for type-safety in client components: ```tsx // searchParams.ts -import { parseAsFloat, createSearchParamsCache } from 'nuqs/server' +import { parseAsFloat } from 'nuqs/server' +import { createSearchParamsCache } from 'nuqs/server/cache' export const coordinatesParsers = { lat: parseAsFloat.withDefault(45.18), diff --git a/errors/NUQS-500.md b/errors/NUQS-500.md index c04571acc..4eeacf54d 100644 --- a/errors/NUQS-500.md +++ b/errors/NUQS-500.md @@ -22,12 +22,8 @@ Run the `parse` method and feed it the page's `searchParams`: ```tsx // page.tsx -import { - createSearchParamsCache, - parseAsInteger, - parseAsString, - type SearchParams -} from 'nuqs/server' +import { parseAsInteger, parseAsString, type SearchParams } from 'nuqs/server' +import { createSearchParamsCache } from 'nuqs/server/cache' const cache = createSearchParamsCache({ q: parseAsString, diff --git a/packages/docs/content/docs/server-side.mdx b/packages/docs/content/docs/server-side.mdx index d5b33625d..037386554 100644 --- a/packages/docs/content/docs/server-side.mdx +++ b/packages/docs/content/docs/server-side.mdx @@ -124,7 +124,7 @@ The loader function will accept the following input types to parse search params ## Cache - This feature is available for Next.js only. + The `next/server/cache` feature is available for Next.js app router only. If you wish to access the searchParams in a deeply nested Server Component @@ -135,12 +135,9 @@ Think of it as a loader combined with a way to propagate the parsed values down the RSC tree, like Context would on the client. ```ts title="searchParams.ts" -import { - createSearchParamsCache, - parseAsInteger, - parseAsString -} from 'nuqs/server' -// Note: import from 'nuqs/server' to avoid the "use client" directive +import { createSearchParamsCache } from 'nuqs/server/cache' +import { parseAsInteger, parseAsString } from 'nuqs/server' +// Note: import parsers from 'nuqs/server' to avoid the "use client" directive export const searchParamsCache = createSearchParamsCache({ // List your search param keys and associated parsers here: @@ -176,6 +173,15 @@ function Results() { } ``` + + The cache feature is also accessible from `nuqs/server` in nuqs@^2, but + will be removed from that import in nuqs@3.0.0. + Please update your imports to `nuqs/server/cache` for a smoother transition. + + This is to allow non-Next.js server code to use the `nuqs/server` import + without having to install React canary for the `cache` function. + + The cache will only be valid for the current page render (see React's [`cache`](https://react.dev/reference/react/cache) function). @@ -183,10 +189,8 @@ Note: the cache only works for **server components**, but you may share your parser declaration with `useQueryStates` for type-safety in client components: ```ts title="searchParams.ts" -import { - parseAsFloat, - createSearchParamsCache -} from 'nuqs/server' +import { parseAsFloat } from 'nuqs/server' +import { createSearchParamsCache } from 'nuqs/server/cache' export const coordinatesParsers = { lat: parseAsFloat.withDefault(45.18), diff --git a/packages/docs/src/app/(pages)/stats/searchParams.ts b/packages/docs/src/app/(pages)/stats/searchParams.ts index f14a0ad06..b1537a080 100644 --- a/packages/docs/src/app/(pages)/stats/searchParams.ts +++ b/packages/docs/src/app/(pages)/stats/searchParams.ts @@ -1,4 +1,5 @@ -import { createSearchParamsCache, parseAsStringLiteral } from 'nuqs/server' +import { parseAsStringLiteral } from 'nuqs/server' +import { createSearchParamsCache } from 'nuqs/server/cache' export const pkgOptions = ['nuqs', 'next-usequerystate', 'both'] as const export const pkgParser = parseAsStringLiteral(pkgOptions).withDefault('both') diff --git a/packages/docs/src/app/playground/(demos)/pagination/searchParams.ts b/packages/docs/src/app/playground/(demos)/pagination/searchParams.ts index 94fe32f48..235500cfe 100644 --- a/packages/docs/src/app/playground/(demos)/pagination/searchParams.ts +++ b/packages/docs/src/app/playground/(demos)/pagination/searchParams.ts @@ -1,9 +1,9 @@ import { - createSearchParamsCache, createSerializer, parseAsInteger, parseAsStringLiteral } from 'nuqs/server' +import { createSearchParamsCache } from 'nuqs/server/cache' export const renderingOptions = ['server', 'client'] as const export type RenderingOptions = (typeof renderingOptions)[number] diff --git a/packages/e2e/next/src/app/app/cache/searchParams.ts b/packages/e2e/next/src/app/app/cache/searchParams.ts index 91ecbe9a1..ec73d6120 100644 --- a/packages/e2e/next/src/app/app/cache/searchParams.ts +++ b/packages/e2e/next/src/app/app/cache/searchParams.ts @@ -1,10 +1,10 @@ import { - createSearchParamsCache, parseAsBoolean, - parseAsInteger, parseAsIndex, + parseAsInteger, parseAsString } from 'nuqs/server' +import { createSearchParamsCache } from 'nuqs/server/cache' export const parsers = { str: parseAsString, diff --git a/packages/e2e/next/src/app/app/push/searchParams.ts b/packages/e2e/next/src/app/app/push/searchParams.ts index 26e0548b2..5de667f15 100644 --- a/packages/e2e/next/src/app/app/push/searchParams.ts +++ b/packages/e2e/next/src/app/app/push/searchParams.ts @@ -1,4 +1,5 @@ -import { createSearchParamsCache, parseAsInteger } from 'nuqs/server' +import { parseAsInteger } from 'nuqs/server' +import { createSearchParamsCache } from 'nuqs/server/cache' export const parser = parseAsInteger.withDefault(0).withOptions({ history: 'push' diff --git a/packages/e2e/next/src/app/app/rewrites/destination/searchParams.ts b/packages/e2e/next/src/app/app/rewrites/destination/searchParams.ts index c1b536be2..cad0729a3 100644 --- a/packages/e2e/next/src/app/app/rewrites/destination/searchParams.ts +++ b/packages/e2e/next/src/app/app/rewrites/destination/searchParams.ts @@ -1,4 +1,5 @@ -import { createSearchParamsCache, parseAsString } from 'nuqs/server' +import { parseAsString } from 'nuqs/server' +import { createSearchParamsCache } from 'nuqs/server/cache' export const searchParams = { injected: parseAsString.withDefault('null'), diff --git a/packages/e2e/next/tsconfig.json b/packages/e2e/next/tsconfig.json index 7d21f37f3..a4ef95503 100644 --- a/packages/e2e/next/tsconfig.json +++ b/packages/e2e/next/tsconfig.json @@ -6,7 +6,7 @@ "alwaysStrict": false, // Don't emit "use strict" to avoid conflicts with "use client" // Modules "module": "ESNext", - "moduleResolution": "node", + "moduleResolution": "bundler", "resolveJsonModule": true, // Language & Environment "target": "ESNext", diff --git a/packages/nuqs/package.json b/packages/nuqs/package.json index fbf08dbbb..2b6485bf3 100644 --- a/packages/nuqs/package.json +++ b/packages/nuqs/package.json @@ -36,6 +36,9 @@ "dist/", "server.d.ts", "testing.d.ts", + "server/cache.d.ts", + "server/serializer.d.ts", + "server/parsers.d.ts", "adapters/react.d.ts", "adapters/next.d.ts", "adapters/next/app.d.ts", @@ -65,7 +68,16 @@ }, "./testing": { "types": "./dist/testing.d.ts", - "import": "./dist/testing.js", + "import": "./dist/testing.js" + }, + "./server/cache": { + "types": "./dist/server/cache.d.ts", + "import": "./dist/server/cache.js", + "require": "./esm-only.cjs" + }, + "./server/temporary-react-agnostic": { + "types": "./dist/server/temporary-react-agnostic.d.ts", + "import": "./dist/server/temporary-react-agnostic.js", "require": "./esm-only.cjs" }, "./adapters/react": { diff --git a/packages/nuqs/server/README.md b/packages/nuqs/server/README.md new file mode 100644 index 000000000..6c226eba3 --- /dev/null +++ b/packages/nuqs/server/README.md @@ -0,0 +1,9 @@ +Why just the cache here? + +Those "top-level" .d.ts files are used to help projects with `moduleResolution: 'node'` +resolve the correct imports. + +The other import under server, `nuqs/server/temporary-react-agnostic` +is temporary (as it says on the tin) and will be removed in nuqs@3.0.0. + +Also, nuqs@3.0.0 will require a `moduleResolution: 'bundler' | 'nodeNext` setting in your tsconfig.json. diff --git a/packages/nuqs/server/cache.d.ts b/packages/nuqs/server/cache.d.ts new file mode 100644 index 000000000..c59f78613 --- /dev/null +++ b/packages/nuqs/server/cache.d.ts @@ -0,0 +1,13 @@ +// This file is needed for projects that have `moduleResolution` set to `node` +// in their tsconfig.json to be able to `import {} from 'nuqs/server/cache'`. +// Other module resolutions strategies will look for the `exports` in `package.json`, +// but with `node`, TypeScript will look for a .d.ts file with that name at the +// root of the package. + +export { createSearchParamsCache } from './dist/server/cache' +export type { + HistoryOptions, + Nullable, + Options, + SearchParams +} from './dist/server/cache' diff --git a/packages/nuqs/src/cache.ts b/packages/nuqs/src/cache.ts index 7cc2a1306..30b06eb28 100644 --- a/packages/nuqs/src/cache.ts +++ b/packages/nuqs/src/cache.ts @@ -1,5 +1,4 @@ -// @ts-ignore -import { cache } from 'react' +import * as React from 'react' import type { SearchParams, UrlKeys } from './defs' import { error } from './errors' import { createLoader } from './loader' @@ -27,7 +26,7 @@ export function createSearchParamsCache( // whereas a simple object would be bound to the lifecycle of the process, // which may be reused between requests in a serverless environment // (warm lambdas on Vercel or AWS). - const getCache = cache<() => Cache>(() => ({ + const getCache = React.cache<() => Cache>(() => ({ searchParams: {} })) diff --git a/packages/nuqs/src/index.server.cache.ts b/packages/nuqs/src/index.server.cache.ts new file mode 100644 index 000000000..c7dd671cf --- /dev/null +++ b/packages/nuqs/src/index.server.cache.ts @@ -0,0 +1 @@ +export { createSearchParamsCache } from './cache' diff --git a/packages/nuqs/src/index.server.ts b/packages/nuqs/src/index.server.ts index 66bdf5c0e..6aa0ed55e 100644 --- a/packages/nuqs/src/index.server.ts +++ b/packages/nuqs/src/index.server.ts @@ -1,4 +1,12 @@ -export { createSearchParamsCache } from './cache' +export { + /** @deprecated Import createSearchParamsCache from 'nuqs/server/cache' instead. + * + * This export will be removed from 'nuqs/server' in nuqs@3.0.0, + * to allow non-Next.js server code to use the parsers, serializeres and other + * server-side utilities without depending on React canary for the `cache` function. + */ + createSearchParamsCache +} from './cache' export type { HistoryOptions, Nullable, diff --git a/packages/nuqs/src/index.temporary-react-agnostic.ts b/packages/nuqs/src/index.temporary-react-agnostic.ts new file mode 100644 index 000000000..ce2da4f51 --- /dev/null +++ b/packages/nuqs/src/index.temporary-react-agnostic.ts @@ -0,0 +1,15 @@ +export type { + HistoryOptions, + Nullable, + Options, + SearchParams, + UrlKeys +} from './defs' +export { + createLoader, + type LoaderFunction, + type LoaderInput, + type LoaderOptions +} from './loader' +export * from './parsers' +export { createSerializer } from './serializer' diff --git a/packages/nuqs/tests/cache.test-d.ts b/packages/nuqs/tests/cache.test-d.ts index 94acd3646..a420deff0 100644 --- a/packages/nuqs/tests/cache.test-d.ts +++ b/packages/nuqs/tests/cache.test-d.ts @@ -1,10 +1,6 @@ import { assertType, describe, expectTypeOf, it } from 'vitest' -import { - createSearchParamsCache, - parseAsBoolean, - parseAsInteger, - parseAsString -} from '../dist/server' +import { parseAsBoolean, parseAsInteger, parseAsString } from '../dist/server' +import { createSearchParamsCache } from '../dist/server/cache' describe('types/cache', () => { const cache = createSearchParamsCache({ diff --git a/packages/nuqs/tsup.config.ts b/packages/nuqs/tsup.config.ts index 50dddbf1c..2c7e8ca5b 100644 --- a/packages/nuqs/tsup.config.ts +++ b/packages/nuqs/tsup.config.ts @@ -35,7 +35,9 @@ const entrypoints = { }, server: { server: 'src/index.server.ts', - testing: 'src/testing.ts' + testing: 'src/testing.ts', + 'server/cache': 'src/index.server.cache.ts', + 'server/temporary-react-agnostic': 'src/index.temporary-react-agnostic.ts' } }