@bb-labs/next-router 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,291 @@
1
+ ## Introduction
2
+
3
+ Type-safe route and search params for Next.js App Router with Zod validation.
4
+
5
+ - 🔒 **Type-safe** — Full TypeScript inference from your Zod schemas
6
+ - ✅ **Validated** — Params are validated at runtime with Zod
7
+ - 🎯 **Flexible** — Use promises or pre-awaited values, your choice
8
+ - 🪝 **Hooks included** — First-class support for client components
9
+ - 📦 **Zero dependencies** — Only requires Zod
10
+ - 🛡️ **Error handling** — Built-in error handling with customizable callbacks
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ npm install @bb-labs/next-router
16
+ # or
17
+ yarn add @bb-labs/next-router
18
+ # or
19
+ bun add @bb-labs/next-router
20
+ ```
21
+
22
+ ## Server Components
23
+
24
+ ### `zPage`
25
+
26
+ A unified function for creating page components with type-safe params. Use the `resolve` parameter to control how params are handled.
27
+
28
+ #### Resolved Params (Default)
29
+
30
+ When `resolve: true` (or omitted), params are **pre-awaited** with built-in Suspense.
31
+
32
+ ```tsx
33
+ import { zPage } from "@bb-labs/next-router";
34
+ import { z } from "zod";
35
+
36
+ export default zPage({
37
+ params: {
38
+ searchParams: z.object({
39
+ email: z.string().email(),
40
+ }),
41
+ },
42
+ handler: ({ searchParams }) => {
43
+ // Already awaited — use directly
44
+ return <div>Email: {searchParams.email}</div>;
45
+ },
46
+ });
47
+ ```
48
+
49
+ **Options for resolved mode:**
50
+
51
+ - `onError?: (error: z.ZodError) => void` — Custom error handler (defaults to redirecting to "/404")
52
+ - `loadingHandler?: React.ReactNode` — Custom Suspense fallback (defaults to `null`)
53
+
54
+ #### Promise Params
55
+
56
+ When `resolve: false`, params are passed as **promises** — you control when to await.
57
+
58
+ ```tsx
59
+ import { zPage, resolveParams } from "@bb-labs/next-router";
60
+ import { z } from "zod";
61
+
62
+ export default zPage({
63
+ params: {
64
+ routeParams: z.object({ id: z.string() }),
65
+ searchParams: z.object({ page: z.coerce.number().optional() }),
66
+ },
67
+ resolve: false,
68
+ handler: async (paramsPromise) => {
69
+ const { routeParams, searchParams } = await resolveParams(paramsPromise);
70
+ return (
71
+ <div>
72
+ Product {routeParams.id}, Page {searchParams.page}
73
+ </div>
74
+ );
75
+ },
76
+ });
77
+ ```
78
+
79
+ **Note:** `onError` and `loadingHandler` are not available when `resolve: false`.
80
+
81
+ ### `resolveParams`
82
+
83
+ Helper function to await and validate params from promise-based handlers. Optionally accepts an `onError` callback.
84
+
85
+ ```tsx
86
+ import { zPage, resolveParams } from "@bb-labs/next-router";
87
+ import { redirect } from "next/navigation";
88
+ import { z } from "zod";
89
+
90
+ export default zPage({
91
+ params: {
92
+ searchParams: z.object({
93
+ email: z.string().email(),
94
+ }),
95
+ },
96
+ resolve: false,
97
+ handler: async (paramsPromise) => {
98
+ const { searchParams } = await resolveParams(paramsPromise, {
99
+ onError: (error) => {
100
+ console.error("Validation failed:", error);
101
+ redirect("/custom-error");
102
+ },
103
+ });
104
+ return <div>{searchParams.email}</div>;
105
+ },
106
+ });
107
+ ```
108
+
109
+ ### Flexible Params Configuration
110
+
111
+ You can provide both, only `routeParams`, or only `searchParams` — but never neither:
112
+
113
+ ```tsx
114
+ // ✅ Both
115
+ { params: { routeParams: z.object({...}), searchParams: z.object({...}) } }
116
+
117
+ // ✅ Only routeParams
118
+ { params: { routeParams: z.object({...}) } }
119
+
120
+ // ✅ Only searchParams
121
+ { params: { searchParams: z.object({...}) } }
122
+
123
+ // ❌ Neither — TypeScript error
124
+ { params: {} }
125
+ ```
126
+
127
+ ---
128
+
129
+ ## Client Components
130
+
131
+ ### `useRouteParams` / `useSearchParams`
132
+
133
+ Hooks for accessing validated params in client components with `onSuccess` and `onError` callbacks.
134
+
135
+ ```tsx
136
+ "use client";
137
+
138
+ import { useSearchParams } from "@bb-labs/next-router/client";
139
+ import { z } from "zod";
140
+
141
+ const searchParamsSchema = z.object({
142
+ email: z.string().email(),
143
+ });
144
+
145
+ export function MyComponent() {
146
+ const { data } = useSearchParams(searchParamsSchema, {
147
+ onSuccess: (data) => {
148
+ console.log("Valid params:", data.email);
149
+ },
150
+ onError: (error) => {
151
+ console.error("Validation failed:", error);
152
+ // Custom error handling (defaults to redirecting to "/404")
153
+ },
154
+ });
155
+
156
+ if (!data) return <div>Loading...</div>;
157
+
158
+ return <div>Email: {data.email}</div>;
159
+ }
160
+ ```
161
+
162
+ ### Hook API
163
+
164
+ ```tsx
165
+ const { data } = useRouteParams(schema, options?);
166
+ const { data } = useSearchParams(schema, options?);
167
+ ```
168
+
169
+ **Options:**
170
+
171
+ - `onSuccess?: (data: T) => void` — Called when params are successfully validated
172
+ - `onError?: (error: Error) => void` — Called when validation fails (defaults to redirecting to "/404")
173
+
174
+ **Returns:**
175
+
176
+ - `data` — The validated params, or `null` if validation failed
177
+
178
+ ---
179
+
180
+ ## Schema Types
181
+
182
+ **Important:** URL params are always strings. Use Zod's coercion methods for non-string types:
183
+
184
+ ```tsx
185
+ // ✅ Correct — use coercion for non-string types
186
+ z.object({
187
+ id: z.string(), // strings work directly
188
+ page: z.coerce.number(), // coerce string → number
189
+ active: z.coerce.boolean(), // coerce string → boolean
190
+ count: z.coerce.number().optional(), // optional coerced number
191
+ tags: z.array(z.string()), // array of strings (for ?tags=a&tags=b)
192
+ });
193
+
194
+ // ❌ Wrong — will fail because raw input is always a string
195
+ z.object({
196
+ page: z.number(), // Error: expected number, received string
197
+ active: z.boolean(), // Error: expected boolean, received string
198
+ });
199
+ ```
200
+
201
+ You can also use transforms for custom parsing:
202
+
203
+ ```tsx
204
+ z.object({
205
+ date: z.string().transform((s) => new Date(s)),
206
+ ids: z.string().transform((s) => s.split(",")),
207
+ });
208
+ ```
209
+
210
+ ### JSON-Encoded Objects
211
+
212
+ For complex objects, encode them as JSON in the URL (URL-escaped). Use `.transform()` with `JSON.parse()` and `.pipe()` to validate:
213
+
214
+ ```tsx
215
+ // URL: ?filter={"category":"books","price":{"min":10,"max":50},"inStock":true}
216
+ // (URL-encoded: ?filter=%7B%22category%22%3A%22books%22...%7D)
217
+
218
+ z.object({
219
+ filter: z
220
+ .string()
221
+ .transform((s) => JSON.parse(s))
222
+ .pipe(
223
+ z.object({
224
+ category: z.string(),
225
+ price: z.object({ min: z.number(), max: z.number() }),
226
+ tags: z.array(z.string()).optional(),
227
+ inStock: z.boolean(),
228
+ })
229
+ ),
230
+ });
231
+ ```
232
+
233
+ **How it works:**
234
+
235
+ 1. The raw URL param is a JSON string: `'{"category":"books",...}'`
236
+ 2. `.transform((s) => JSON.parse(s))` parses it into a JS object
237
+ 3. `.pipe(z.object({...}))` validates the parsed object's structure
238
+
239
+ **Example usage:**
240
+
241
+ ```tsx
242
+ export default zPage({
243
+ params: {
244
+ searchParams: z.object({
245
+ filter: z
246
+ .string()
247
+ .transform((s) => JSON.parse(s))
248
+ .pipe(
249
+ z.object({
250
+ category: z.string(),
251
+ price: z.object({ min: z.number(), max: z.number() }),
252
+ })
253
+ ),
254
+ }),
255
+ },
256
+ handler: ({ searchParams }) => {
257
+ // searchParams.filter is fully typed!
258
+ return (
259
+ <div>
260
+ Category: {searchParams.filter.category}
261
+ Price: ${searchParams.filter.price.min} - ${searchParams.filter.price.max}
262
+ </div>
263
+ );
264
+ },
265
+ });
266
+ ```
267
+
268
+ **Client-side encoding:**
269
+
270
+ ```tsx
271
+ const filter = { category: "books", price: { min: 10, max: 50 } };
272
+ const url = `/products?filter=${encodeURIComponent(JSON.stringify(filter))}`;
273
+ ```
274
+
275
+ ---
276
+
277
+ ## Error Handling
278
+
279
+ By default, validation errors redirect to "/404":
280
+
281
+ - **Server components**: `zPage` and `resolveParams` with `resolve: true` redirects on validation failure
282
+ - **Client components**: `useRouteParams` and `useSearchParams` redirect on validation failure
283
+
284
+ You can customize error handling:
285
+
286
+ - **Server**: Provide an `onError` callback to `zPage` (only available when `resolve: true`)
287
+ - **Client**: Provide an `onError` callback to the hooks
288
+
289
+ ## License
290
+
291
+ MIT
@@ -0,0 +1 @@
1
+ export * from "./z-page";
@@ -0,0 +1 @@
1
+ export * from "./z-page";
@@ -0,0 +1,21 @@
1
+ import { z, type ZodRawShape } from "zod";
2
+ type ZodObject = z.ZodObject<ZodRawShape>;
3
+ type HookOptions<T> = {
4
+ onSuccess?: (data: T) => void;
5
+ onError?: (error: Error) => void;
6
+ };
7
+ export declare function useRouteParams<T extends ZodObject>(schema: T, options?: HookOptions<z.output<T>>): {
8
+ data: z.output<T>;
9
+ error: null;
10
+ } | {
11
+ data: null;
12
+ error: z.ZodError<z.core.output<T>>;
13
+ };
14
+ export declare function useSearchParams<T extends ZodObject>(schema: T, options?: HookOptions<z.output<T>>): {
15
+ data: z.output<T>;
16
+ error: null;
17
+ } | {
18
+ data: null;
19
+ error: z.ZodError<z.core.output<T>>;
20
+ };
21
+ export {};
@@ -0,0 +1,52 @@
1
+ "use client";
2
+ import { useEffect, useRef, useMemo } from "react";
3
+ import { useParams, useSearchParams as useNextSearchParams, useRouter } from "next/navigation";
4
+ export function useRouteParams(schema, options) {
5
+ const rawParams = useParams();
6
+ const router = useRouter();
7
+ const calledRef = useRef(false);
8
+ const result = useMemo(() => {
9
+ const parsed = schema.safeParse(rawParams);
10
+ return parsed.success ? { data: parsed.data, error: null } : { data: null, error: parsed.error };
11
+ }, [rawParams, schema]);
12
+ useEffect(() => {
13
+ if (calledRef.current)
14
+ return;
15
+ calledRef.current = true;
16
+ if (result.error) {
17
+ console.log(result.error);
18
+ options?.onError ? options.onError(result.error) : router.push("/404");
19
+ }
20
+ else if (result.data) {
21
+ options?.onSuccess?.(result.data);
22
+ }
23
+ }, [result, options, router]);
24
+ return result;
25
+ }
26
+ export function useSearchParams(schema, options) {
27
+ const nextSearchParams = useNextSearchParams();
28
+ const router = useRouter();
29
+ const calledRef = useRef(false);
30
+ const result = useMemo(() => {
31
+ const raw = {};
32
+ nextSearchParams.forEach((v, k) => {
33
+ const existing = raw[k];
34
+ raw[k] = existing ? (Array.isArray(existing) ? [...existing, v] : [existing, v]) : v;
35
+ });
36
+ const parsed = schema.safeParse(raw);
37
+ return parsed.success ? { data: parsed.data, error: null } : { data: null, error: parsed.error };
38
+ }, [nextSearchParams, schema]);
39
+ useEffect(() => {
40
+ if (calledRef.current)
41
+ return;
42
+ calledRef.current = true;
43
+ if (result.error) {
44
+ console.log(result.error);
45
+ options?.onError ? options.onError(result.error) : router.push("/404");
46
+ }
47
+ else if (result.data) {
48
+ options?.onSuccess?.(result.data);
49
+ }
50
+ }, [result, options, router]);
51
+ return result;
52
+ }
@@ -0,0 +1 @@
1
+ export * from "./z-page";
@@ -0,0 +1 @@
1
+ export * from "./z-page";
@@ -0,0 +1,81 @@
1
+ import React from "react";
2
+ import { z } from "zod";
3
+ import { type ZodObject, type OnError } from "../utils/helper";
4
+ import type { NextPageProps } from "../types";
5
+ /** Return type for zPage */
6
+ type ZPageReturn = (props: NextPageProps) => React.ReactNode | Promise<React.ReactNode>;
7
+ export declare function zPage<RP extends ZodObject, SP extends ZodObject>(cfg: {
8
+ params: {
9
+ routeParams: RP;
10
+ searchParams: SP;
11
+ };
12
+ resolve?: true;
13
+ onError?: OnError;
14
+ loadingHandler?: React.ReactNode;
15
+ handler: (input: {
16
+ routeParams: z.output<RP>;
17
+ searchParams: z.output<SP>;
18
+ }) => React.ReactNode | Promise<React.ReactNode>;
19
+ }): ZPageReturn;
20
+ export declare function zPage<RP extends ZodObject>(cfg: {
21
+ params: {
22
+ routeParams: RP;
23
+ searchParams?: undefined;
24
+ };
25
+ resolve?: true;
26
+ onError?: OnError;
27
+ loadingHandler?: React.ReactNode;
28
+ handler: (input: {
29
+ routeParams: z.output<RP>;
30
+ }) => React.ReactNode | Promise<React.ReactNode>;
31
+ }): ZPageReturn;
32
+ export declare function zPage<SP extends ZodObject>(cfg: {
33
+ params: {
34
+ routeParams?: undefined;
35
+ searchParams: SP;
36
+ };
37
+ resolve?: true;
38
+ onError?: OnError;
39
+ loadingHandler?: React.ReactNode;
40
+ handler: (input: {
41
+ searchParams: z.output<SP>;
42
+ }) => React.ReactNode | Promise<React.ReactNode>;
43
+ }): ZPageReturn;
44
+ export declare function zPage<RP extends ZodObject, SP extends ZodObject>(cfg: {
45
+ params: {
46
+ routeParams: RP;
47
+ searchParams: SP;
48
+ };
49
+ resolve: false;
50
+ onError?: never;
51
+ loadingHandler?: never;
52
+ handler: (input: {
53
+ routeParams: Promise<z.output<RP>>;
54
+ searchParams: Promise<z.output<SP>>;
55
+ }) => React.ReactNode | Promise<React.ReactNode>;
56
+ }): ZPageReturn;
57
+ export declare function zPage<RP extends ZodObject>(cfg: {
58
+ params: {
59
+ routeParams: RP;
60
+ searchParams?: undefined;
61
+ };
62
+ resolve: false;
63
+ onError?: never;
64
+ loadingHandler?: never;
65
+ handler: (input: {
66
+ routeParams: Promise<z.output<RP>>;
67
+ }) => React.ReactNode | Promise<React.ReactNode>;
68
+ }): ZPageReturn;
69
+ export declare function zPage<SP extends ZodObject>(cfg: {
70
+ params: {
71
+ routeParams?: undefined;
72
+ searchParams: SP;
73
+ };
74
+ resolve: false;
75
+ onError?: never;
76
+ loadingHandler?: never;
77
+ handler: (input: {
78
+ searchParams: Promise<z.output<SP>>;
79
+ }) => React.ReactNode | Promise<React.ReactNode>;
80
+ }): ZPageReturn;
81
+ export {};
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Suspense } from "react";
3
+ import { setup } from "../utils/helper";
4
+ // ------------- IMPLEMENTATION -------------
5
+ export function zPage(cfg) {
6
+ const shouldResolve = cfg.resolve !== false;
7
+ const { createValidatedPromises, resolveParams } = setup(cfg.params, shouldResolve ? cfg.onError : undefined);
8
+ if (!shouldResolve) {
9
+ return async (props) => cfg.handler(createValidatedPromises(props));
10
+ }
11
+ async function Inner({ promises }) {
12
+ const input = await resolveParams(promises);
13
+ if (!Object.keys(input).length)
14
+ return null;
15
+ return cfg.handler(input);
16
+ }
17
+ return (props) => (_jsx(Suspense, { fallback: cfg.loadingHandler ?? null, children: _jsx(Inner, { promises: createValidatedPromises(props) }) }));
18
+ }
@@ -0,0 +1,12 @@
1
+ import { z, type ZodObject, type ZodRawShape } from "zod";
2
+ type InferParamsPromises<T> = ("routeParams" extends keyof T ? (T["routeParams"] extends ZodObject<ZodRawShape> ? {
3
+ routeParams: Promise<z.output<T["routeParams"]>>;
4
+ } : {}) : {}) & ("searchParams" extends keyof T ? (T["searchParams"] extends ZodObject<ZodRawShape> ? {
5
+ searchParams: Promise<z.output<T["searchParams"]>>;
6
+ } : {}) : {});
7
+ type InferParams<T> = ("routeParams" extends keyof T ? (T["routeParams"] extends ZodObject<ZodRawShape> ? {
8
+ routeParams: z.output<T["routeParams"]>;
9
+ } : {}) : {}) & ("searchParams" extends keyof T ? (T["searchParams"] extends ZodObject<ZodRawShape> ? {
10
+ searchParams: z.output<T["searchParams"]>;
11
+ } : {}) : {});
12
+ export type { InferParamsPromises, InferParams };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,14 @@
1
+ import { type OnError } from "../utils/helper";
2
+ type ResolveParamsInput<RP, SP> = {
3
+ routeParams?: Promise<RP>;
4
+ searchParams?: Promise<SP>;
5
+ };
6
+ type ResolveParamsResult<RP, SP> = {
7
+ routeParams: RP;
8
+ searchParams: SP;
9
+ };
10
+ type ResolveParamsOptions = {
11
+ onError?: OnError;
12
+ };
13
+ export declare function resolveParams<RP, SP>(paramsPromise: ResolveParamsInput<RP, SP>, options?: ResolveParamsOptions): Promise<ResolveParamsResult<RP, SP>>;
14
+ export {};
@@ -0,0 +1,31 @@
1
+ import { z } from "zod";
2
+ import { defaultOnError } from "../utils/helper";
3
+ export async function resolveParams(paramsPromise, options = {}) {
4
+ const result = {};
5
+ const onError = options.onError ?? defaultOnError;
6
+ if (paramsPromise.routeParams) {
7
+ try {
8
+ result.routeParams = await paramsPromise.routeParams;
9
+ }
10
+ catch (e) {
11
+ if (e instanceof z.ZodError) {
12
+ onError(e);
13
+ return result;
14
+ }
15
+ throw e;
16
+ }
17
+ }
18
+ if (paramsPromise.searchParams) {
19
+ try {
20
+ result.searchParams = await paramsPromise.searchParams;
21
+ }
22
+ catch (e) {
23
+ if (e instanceof z.ZodError) {
24
+ onError(e);
25
+ return result;
26
+ }
27
+ throw e;
28
+ }
29
+ }
30
+ return result;
31
+ }
@@ -0,0 +1,3 @@
1
+ export { zPage } from "./fns/z-page";
2
+ export { resolveParams } from "./helpers/resolve-params";
3
+ export type { InferParamsPromises, InferParams } from "./helpers/infer-props";
@@ -0,0 +1,2 @@
1
+ export { zPage } from "./fns/z-page";
2
+ export { resolveParams } from "./helpers/resolve-params";
@@ -0,0 +1,10 @@
1
+ import { z, type ZodRawShape } from "zod";
2
+ /** Next.js page props type */
3
+ export type NextPageProps = {
4
+ params: Promise<Record<string, string | string[]>>;
5
+ searchParams: Promise<Record<string, string | string[] | undefined>>;
6
+ };
7
+ /** Enforces that routeParams/searchParams must be z.object(...) */
8
+ export type ZodObject = z.ZodObject<ZodRawShape>;
9
+ /** Error handler type */
10
+ export type OnError = (error: z.ZodError) => never | void;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,17 @@
1
+ import { z } from "zod";
2
+ import type { NextPageProps, ZodObject, OnError } from "../types";
3
+ export type { ZodObject, OnError };
4
+ export declare const defaultOnError: OnError;
5
+ export declare function setup<RP extends ZodObject, SP extends ZodObject>(params: {
6
+ routeParams?: RP;
7
+ searchParams?: SP;
8
+ }, onError?: OnError): {
9
+ createValidatedPromises: (props: NextPageProps) => {
10
+ routeParams: Promise<z.core.output<RP>> | undefined;
11
+ searchParams: Promise<z.core.output<SP>> | undefined;
12
+ };
13
+ resolveParams: (promises: {
14
+ routeParams: Promise<z.core.output<RP>> | undefined;
15
+ searchParams: Promise<z.core.output<SP>> | undefined;
16
+ }) => Promise<Record<string, unknown>>;
17
+ };
@@ -0,0 +1,35 @@
1
+ import { z } from "zod";
2
+ import { redirect } from "next/navigation";
3
+ export const defaultOnError = (e) => {
4
+ console.log(e);
5
+ redirect("/404");
6
+ };
7
+ export function setup(params, onError = defaultOnError) {
8
+ const rpSchema = params.routeParams;
9
+ const spSchema = params.searchParams;
10
+ /** Creates validated promises from Next.js page props */
11
+ function createValidatedPromises(props) {
12
+ return {
13
+ routeParams: rpSchema ? props.params.then((raw) => rpSchema.parse(raw)) : undefined,
14
+ searchParams: spSchema ? props.searchParams.then((raw) => spSchema.parse(raw)) : undefined,
15
+ };
16
+ }
17
+ /** Awaits all params in parallel, handling errors */
18
+ async function resolveParams(promises) {
19
+ const result = {};
20
+ try {
21
+ const [rp, sp] = await Promise.all([promises.routeParams, promises.searchParams]);
22
+ if (rpSchema)
23
+ result.routeParams = rp;
24
+ if (spSchema)
25
+ result.searchParams = sp;
26
+ }
27
+ catch (e) {
28
+ if (e instanceof z.ZodError)
29
+ return onError(e), {};
30
+ throw e;
31
+ }
32
+ return result;
33
+ }
34
+ return { createValidatedPromises, resolveParams };
35
+ }
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@bb-labs/next-router",
3
+ "version": "0.0.1",
4
+ "author": "Beepbop",
5
+ "homepage": "https://github.com/beepbop-labs/next-router",
6
+ "keywords": [
7
+ "next-router",
8
+ "router",
9
+ "next",
10
+ "nextjs"
11
+ ],
12
+ "license": "MIT",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://github.com/beepbop-labs/next-router.git"
16
+ },
17
+ "description": "A library for routing in Next.js",
18
+ "files": [
19
+ "dist",
20
+ "README.md"
21
+ ],
22
+ "exports": {
23
+ ".": "./dist/server/index.js",
24
+ "./client": "./dist/client/index.js"
25
+ },
26
+ "scripts": {
27
+ "clean": "rm -rf dist",
28
+ "build": "npm run clean && tsc -p tsconfig.json",
29
+ "pack": "npm run build && npm pack --pack-destination ./archive/"
30
+ },
31
+ "dependencies": {
32
+ "zod": "^4.2.1"
33
+ },
34
+ "devDependencies": {
35
+ "@types/bun": "latest",
36
+ "@types/react": "^19.2.7"
37
+ },
38
+ "peerDependencies": {
39
+ "typescript": "^5",
40
+ "react": "^19",
41
+ "next": "^16.0.4"
42
+ }
43
+ }