Skip to content

Commit 9d2a514

Browse files
committed
doc: v2 announcement blog post (#687)
* doc: Add blog engine from Fumadocs * doc: Add v2 announcement banners * doc: Make blog posts look pretty * doc: Blog post contents * doc: Add og:images * doc: Add blog post og:image * doc: Wording fixes * doc: Add v2 blog link to playground
1 parent f198605 commit 9d2a514

36 files changed

+404
-111
lines changed

packages/docs/content/blog/nuqs-2.mdx

+162
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
---
2+
title: nuqs 2
3+
description: Opening up to other React frameworks
4+
author: François Best
5+
date: 2024-10-22
6+
---
7+
8+
nuqs@2.0.0 is available, try it now:
9+
10+
```bash
11+
pnpm add nuqs@latest
12+
```
13+
14+
It's packing exciting features & improvements, including:
15+
16+
- [Support for other React frameworks](#hello-react): Next.js, React SPA, Remix, React Router, and more to come
17+
- A built-in [testing adapter](#testing) to unit-test your components in isolation
18+
- [Bundle size improvements](#bundle-size-improvements)
19+
- Interactive documentation, with [community parsers](/docs/parsers/community)
20+
21+
<hr/>
22+
23+
## Hello, React! 👋 ⚛️ [#hello-react]
24+
25+
nuqs started as a Next.js-only hook, and v2 brings compatibility for other React frameworks:
26+
27+
- Next.js 14 & 15 (app & pages routers)
28+
- React SPA
29+
- Remix
30+
- React Router
31+
32+
No code change is necessary in components that use nuqs hooks,
33+
making them **universal** across all supported frameworks.
34+
35+
The only new requirement is to wrap your React tree with an
36+
[adapter](/docs/adapters) for your framework.
37+
38+
Example for a React SPA with Vite:
39+
40+
```tsx title="src/main.tsx"
41+
// [!code word:NuqsAdapter]
42+
import { NuqsAdapter } from 'nuqs/adapters/react'
43+
44+
createRoot(document.getElementById('root')!).render(
45+
<NuqsAdapter>
46+
<App />
47+
</NuqsAdapter>
48+
)
49+
```
50+
51+
<Callout>
52+
The [adapters documentation](/docs/adapters) has examples for all supported frameworks.
53+
</Callout>
54+
55+
## Testing
56+
57+
One of the major pain points with nuqs v1 was testing components that used its hooks.
58+
59+
Nuqs v2 comes with a built-in [testing adapter](/docs/testing) that mocks URL behaviours,
60+
allowing you to test your components in isolation, outside of any framework runtime.
61+
62+
You can use it with any unit testing framework that renders React components
63+
(I recommend [Vitest](https://vitest.dev) & [Testing Library](https://testing-library.com/)).
64+
65+
```tsx title="counter-button.test.tsx"
66+
// [!code word:NuqsTestingAdapter]
67+
import { render, screen } from '@testing-library/react'
68+
import userEvent from '@testing-library/user-event'
69+
import { NuqsTestingAdapter, type UrlUpdateEvent } from 'nuqs/adapters/testing'
70+
import { describe, expect, it, vi } from 'vitest'
71+
import { CounterButton } from './counter-button'
72+
73+
it('should increment the count when clicked', async () => {
74+
const user = userEvent.setup()
75+
const onUrlUpdate = vi.fn<[UrlUpdateEvent]>()
76+
render(<CounterButton />, {
77+
// 1. Setup the test by passing initial search params / querystring:
78+
wrapper: ({ children }) => (
79+
<NuqsTestingAdapter searchParams="?count=42" onUrlUpdate={onUrlUpdate}>
80+
{children}
81+
</NuqsTestingAdapter>
82+
)
83+
})
84+
// 2. Act
85+
const button = screen.getByRole('button')
86+
await user.click(button)
87+
// 3. Assert changes in the state and in the (mocked) URL
88+
expect(button).toHaveTextContent('count is 43')
89+
expect(onUrlUpdate).toHaveBeenCalledOnce()
90+
expect(onUrlUpdate.mock.calls[0][0].queryString).toBe('?count=43')
91+
expect(onUrlUpdate.mock.calls[0][0].searchParams.get('count')).toBe('43')
92+
expect(onUrlUpdate.mock.calls[0][0].options.history).toBe('push')
93+
})
94+
```
95+
96+
The adapter conforms to the **setup** / **act** / **assert** testing strategy, allowing you
97+
to:
98+
99+
1. Set the initial URL search params
100+
2. Let your test framework perform actions on your component
101+
3. Asserting on how the URL was changed as a result
102+
103+
## Breaking changes & migration
104+
105+
The biggest breaking change is the introduction of [adapters](/docs/adapters).
106+
Another one is related to deprecated APIs.
107+
108+
The `next-usequerystate` package that started this journey is no longer updated.
109+
All updates are now published under the `nuqs` package name.
110+
111+
The minimum version of Next.js supported is now 14.2.0. It is compatible with
112+
Next.js 15, including the async `searchParams{:ts}` page prop in the [server-side cache](/docs/server-side).
113+
114+
There are some important behaviour changes, based on feedback from the community:
115+
116+
- [`clearOnDefault{:ts}`](/docs/options#clear-on-default) is now `true{:ts}` by default
117+
- [`startTransition{:ts}`](/docs/options#transitions) no longer sets `shallow: false{:ts}`
118+
- [`parseAsJson{:ts}`](/docs/parsers/built-in#json) now requires a validation function
119+
120+
<Callout>
121+
Read the complete [migration guide](/docs/migrations/v2) to update your applications.
122+
</Callout>
123+
124+
## Bundle size improvements
125+
126+
By moving to **ESM-only**, and dropping hacks needed to support older versions of Next.js,
127+
the bundle size is now **20% smaller** than v1. It's also **side-effects free** and **tree-shakable**.
128+
129+
## What's next?
130+
131+
The community and I have a lot of ideas for the future of nuqs, including:
132+
133+
- A unified, scalable, type-safe routing experience in all supported React frameworks
134+
- Community-contributed parsers & adapters
135+
- New options: debouncing, global defaults override
136+
- Middleware to migrate old URLs to new ones
137+
- Better Zod integration for type-safe & runtime-safe validation
138+
139+
## Thanks
140+
141+
I want to thank [sponsors](https://github.com/sponsors/franky47),
142+
[contributors](https://github.com/47ng/nuqs/graphs/contributors)
143+
and people who raised issues and discussions on
144+
[GitHub](https://github.com/47ng/nuqs) and [X/Twitter](https://x.com/nuqs47ng).
145+
You are the growing community that drives this project forward,
146+
and I couldn't be happier with the response.
147+
148+
### Sponsors
149+
150+
- [Pontus Abrahamsson](https://x.com/pontusab), founder of [Midday.ai](https://midday.ai)
151+
- [Carl Lindesvard](https://x.com/CarlLindesvard), founder of [OpenPanel](https://openpanel.dev)
152+
- [Robin Wieruch](https://x.com/rwieruch), author of [The Road to Next](https://www.road-to-next.com/)
153+
- [Yoann Fleury](https://x.com/YoannFleuryDev)
154+
- [Sunghyun Cho](https://github.com/anaclumos)
155+
- [Jalol](https://github.com/mirislomovmirjalol)
156+
157+
Thanks to these amazing people, I'm able to dedicate more time to this project and make it better for everyone.
158+
Join them on [GitHub Sponsors](https://github.com/sponsors/franky47)!
159+
160+
### Contributors
161+
162+
Huge thanks to [@andreisocaciu](https://github.com/andreisocaciu), [@tordans](https://github.com/tordans), [@prasannamestha](https://github.com/prasannamestha), [@Talent30](https://github.com/Talent30), [@neefrehman](https://github.com/neefrehman), [@chbg](https://github.com/chbg), [@dopry](https://github.com/dopry), [@weisisheng](https://github.com/weisisheng), [@hugotiger](https://github.com/hugotiger), [@iuriizaporozhets](https://github.com/iuriizaporozhets), [@rikbrown](https://github.com/rikbrown), [@mateogianolio](https://github.com/mateogianolio), [@timheerwagen](https://github.com/timheerwagen), [@psdmsft](https://github.com/psdmsft), and [@psdewar](https://github.com/psdewar) for helping!

packages/docs/content/docs/adapters.mdx

+26-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@ wrapping it with a `NuqsAdapter` context provider:
1212
- [Remix](#remix)
1313
- [React Router](#react-router)
1414

15-
## Next.js (app router)
15+
## Next.js
16+
17+
### App router [#nextjs-app-router]
18+
19+
Wrap your `{children}{:ts}` with the `NuqsAdapter{:ts}` component in your root layout file:
1620

1721
```tsx title="src/app/layout.tsx"
1822
// [!code word:NuqsAdapter]
@@ -34,7 +38,9 @@ export default function RootLayout({
3438
}
3539
```
3640

37-
## Next.js (pages router)
41+
### Pages router [#nextjs-pages-router]
42+
43+
Wrap the `<Component>{:ts}` page outlet with the `NuqsAdapter{:ts}` component in your `_app.tsx` file:
3844

3945
```tsx title="src/pages/_app.tsx"
4046
// [!code word:NuqsAdapter]
@@ -50,6 +56,18 @@ export default function MyApp({ Component, pageProps }: AppProps) {
5056
}
5157
```
5258

59+
### Unified (router-agnostic) [#nextjs-unified]
60+
61+
If your Next.js app uses **both the app and pages routers** and the adapter needs
62+
to be mounted in either, you can import the unified adapter, at the cost
63+
of a slightly larger bundle size (~100B).
64+
65+
```tsx
66+
import { NuqsAdapter } from 'nuqs/adapters/next'
67+
```
68+
69+
<br/>
70+
5371
The main reason for adapters is to open up nuqs to other React frameworks:
5472

5573
## React SPA
@@ -107,3 +125,9 @@ export function ReactRouter() {
107125
)
108126
}
109127
```
128+
129+
## Testing
130+
131+
<Callout>
132+
Documentation for the `NuqsTestingAdapter{:ts}` is on the [testing page](/docs/testing).
133+
</Callout>

packages/docs/content/docs/installation.mdx

+5-3
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,14 @@ bun add nuqs
2323

2424
## Which version should I use?
2525

26-
`nuqs` supports the following frameworks and their respective versions:
26+
`nuqs@^2` supports the following frameworks and their respective versions:
2727

28-
- [Next.js](./adapters#nextjs-app-router): 14.2.0 and above (including Next.js 15)
28+
- [Next.js](./adapters#nextjs): 14.2.0 and above (including Next.js 15)
2929
- [React SPA](./adapters#react-spa): 18.3.0 & 19 RC
3030
- [Remix](./adapters#remix): 2 and above
3131
- [React Router](./adapters#react-router): 6 and above
3232

33-
For older versions of Next.js, you may use `nuqs@^1`.
33+
<Callout>
34+
For older versions of Next.js, you may use `nuqs@^1` (documentation in the README).
35+
</Callout>
3436

packages/docs/content/docs/migrations/v2.mdx

+3-5
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ export default function MyApp({ Component, pageProps }: AppProps) {
8181
#### Unified (router-agnostic)
8282

8383
If your Next.js app uses **both the app and pages routers** and the adapter needs
84-
to be mounted in either, you can use the unified adapter, at the cost
84+
to be mounted in either, you can import the unified adapter, at the cost
8585
of a slightly larger bundle size (~100B).
8686

8787
```tsx
@@ -93,8 +93,6 @@ import { NuqsAdapter } from 'nuqs/adapters/next'
9393
Albeit not part of a migration from v1, you can now use nuqs in other React
9494
frameworks via their respective [adapters](/docs/adapters).
9595

96-
{/* todo: Add the docs/adapters page */}
97-
9896
However, there's one more adapter that might be of interest to you, and solves
9997
a long-standing issue with testing components using nuqs hooks:
10098

@@ -188,7 +186,7 @@ const { useQueryState } = await import('nuqs')
188186
Some of the v1 API was marked as deprecated back in September 2023, and has been
189187
removed in `nuqs@2.0.0`.
190188

191-
### `queryTypes{:ts}` parsers object
189+
### `queryTypes` parsers object
192190

193191
The `queryTypes{:ts}` object has been removed in favor of individual parser exports,
194192
for better tree-shaking.
@@ -205,7 +203,7 @@ Replace with `parseAsXYZ{:ts}` to match:
205203
+ useQueryState('page', parseAsInteger.withDefault(1))
206204
```
207205

208-
### `subscribeToQueryUpdates{:ts}`
206+
### `subscribeToQueryUpdates`
209207

210208
Next.js 14.1.0 makes `useSearchParams{:ts}` reactive to shallow search params updates,
211209
which makes this internal helper function redundant. See [#425](https://github.com/47ng/nuqs/pull/425) for context.

packages/docs/content/docs/server-side.mdx

+14-9
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,12 @@ title: Server-Side usage
33
description: Type-safe search params on the server
44
---
55

6+
<Callout>
7+
This feature is available for Next.js only.
8+
</Callout>
9+
610
If you wish to access the searchParams in a deeply nested Server Component
7-
(ie: not in the Page component), you can use `createSearchParamsCache`
11+
(ie: not in the Page component), you can use `createSearchParamsCache{:ts}`
812
to do so in a type-safe manner.
913

1014
<Callout type="warn" title="Note">
@@ -32,15 +36,16 @@ export const searchParamsCache = createSearchParamsCache({
3236

3337
```tsx title="page.tsx"
3438
import { searchParamsCache } from './searchParams'
39+
import { type SearchParams } from 'nuqs/server'
40+
41+
type PageProps = {
42+
searchParams: Promise<SearchParams> // Next.js 15+: async searchParams prop
43+
}
3544

36-
export default function Page({
37-
searchParams
38-
}: {
39-
searchParams: Record<string, string | string[] | undefined>
40-
}) {
45+
export default async function Page({ searchParams }: PageProps) {
4146
// ⚠️ Don't forget to call `parse` here.
4247
// You can access type-safe values from the returned object:
43-
const { q: query } = searchParamsCache.parse(searchParams)
48+
const { q: query } = searchParamsCache.parse(await searchParams)
4449
return (
4550
<div>
4651
<h1>Search Results for {query}</h1>
@@ -81,8 +86,8 @@ import { coordinatesCache } from './searchParams'
8186
import { Server } from './server'
8287
import { Client } from './client'
8388

84-
export default function Page({ searchParams }) {
85-
coordinatesCache.parse(searchParams)
89+
export default async function Page({ searchParams }) {
90+
coordinatesCache.parse(await searchParams)
8691
return (
8792
<>
8893
<Server />

0 commit comments

Comments
 (0)