Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .changeset/wicked-cooks-argue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
'next-safe-navigation': minor
---

### Standard Schema Support

This release introduces support for [Standard Schema](https://github.com/standard-schema/standard-schema), allowing you to use your favorite validation library for defining route params and search query schemas.

Previously, `next-safe-navigation` was tightly coupled with `zod`. Now, you can use any `standard-schema` compatible library, such as `valibot`, `arktype`, and others, while `zod` continues to be supported out-of-the-box.

For existing users, your `zod` schemas will continue to work without any changes.

For new users or those wishing to switch, you can now use other libraries. For example, using `valibot`:

```ts
// src/shared/navigation.ts
import { createNavigationConfig } from 'next-safe-navigation';
import * as v from 'valibot';

export const { routes, useSafeParams, useSafeSearchParams } =
createNavigationConfig((defineRoute) => ({
customers: defineRoute('/customers', {
search: v.object({
query: v.optional(v.string(), ''),
page: v.optional(v.pipe(v.string(), v.transform(Number)), 1),
}),
}),
// ...
}));
```
112 changes: 102 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,33 +17,44 @@
</p>

<p align="center">
<strong>Static type and runtime validation for navigating routes in <a href="https://nextjs.org" target="\_parent">NextJS App Router</a> with Zod schemas.</strong>
<strong>Static type and runtime validation for navigating routes in <a href="https://nextjs.org" target="\_parent">NextJS App Router</a> with Standard Schema support (Zod, Valibot, ArkType, etc.).</strong>
</p>

<p align="center">
Static and runtime validation of routes, route params and query string parameters on client and server components.
</p>

## 📦 Install

Safe NextJS Navigation is available as a package on NPM, install with your favorite package manager:

```dircolors
npm install next-safe-navigation
```

You'll also need to install a Standard Schema compatible validation library:

```dircolors
# Choose one:
npm install zod # Zod (most popular)
npm install valibot # Valibot (lightweight)
npm install arktype # ArkType (fast)
```

## ⚡ Quick start

> [!TIP]
> Enable `experimental.typedRoutes` in `next.config.js` for a better and safer experience with autocomplete when defining your routes

### Declare your application routes and parameters in a single place

```ts
// src/shared/navigation.ts
import { createNavigationConfig } from "next-safe-navigation";
import { z } from "zod";
import { createNavigationConfig } from 'next-safe-navigation';
import { z } from 'zod';

export const { routes, useSafeParams, useSafeSearchParams } = createNavigationConfig(
(defineRoute) => ({
export const { routes, useSafeParams, useSafeSearchParams } =
createNavigationConfig((defineRoute) => ({
home: defineRoute('/'),
customers: defineRoute('/customers', {
search: z
Expand All @@ -70,19 +81,21 @@ export const { routes, useSafeParams, useSafeSearchParams } = createNavigationCo
slug: z.array(z.string()).optional(),
}),
}),
}),
);
}));
```

### Runtime validation for React Server Components (RSC)

> [!IMPORTANT]
> The output of a Zod schema might not be the same as its input, since schemas can transform the values during parsing (e.g.: `z.coerce.number()`), especially when dealing with `URLSearchParams` where all values are strings and you might want to convert params to different types. For this reason, this package does not expose types to infer `params` or `searchParams` from your declared routes to be used in page props:
> The output of a schema might not be the same as its input, since schemas can transform the values during parsing (e.g.: string to number coercion), especially when dealing with `URLSearchParams` where all values are strings and you might want to convert params to different types. For this reason, this package does not expose types to infer `params` or `searchParams` from your declared routes to be used in page props:
>
> ```ts
> interface CustomersPageProps {
> // ❌ Do not declare your params | searchParam types
> searchParams?: ReturnType<typeof routes.customers.$parseSearchParams>
> searchParams?: ReturnType<typeof routes.customers.$parseSearchParams>;
> }
>```
> ```
>
> Instead, it is strongly advised that you parse the params in your server components to have runtime validated and accurate type information for the values in your app.

```ts
Expand Down Expand Up @@ -132,6 +145,7 @@ export default async function InvoicePage({ params }: InvoicePageProps) {
```

### Runtime validation for Client Components

```ts
// src/app/customers/page.tsx
'use client';
Expand Down Expand Up @@ -206,3 +220,81 @@ export function CustomerInvoices({ invoices }) {
)
};
```

## 🔄 Standard Schema Support

This library now supports [Standard Schema](https://github.com/standard-schema/standard-schema), which means you can use any compatible validation library:

### Using Zod

```ts
// src/shared/navigation.ts
import { createNavigationConfig } from 'next-safe-navigation';
import { z } from 'zod';

export const { routes, useSafeParams, useSafeSearchParams } =
createNavigationConfig((defineRoute) => ({
customers: defineRoute('/customers', {
search: z
.object({
query: z.string().default(''),
page: z.coerce.number().default(1),
})
.default({ query: '', page: 1 }),
}),
invoice: defineRoute('/invoices/[invoiceId]', {
params: z.object({
invoiceId: z.string(),
}),
}),
}));
```

### Using Valibot

```ts
// src/shared/navigation.ts
import { createNavigationConfig } from 'next-safe-navigation';
import * as v from 'valibot';

export const { routes, useSafeParams, useSafeSearchParams } =
createNavigationConfig((defineRoute) => ({
customers: defineRoute('/customers', {
search: v.objectWithRest(
{
query: v.optional(v.string(), ''),
page: v.optional(v.pipe(v.string(), v.transform(Number)), 1),
},
v.never(),
),
}),
invoice: defineRoute('/invoices/[invoiceId]', {
params: v.object({
invoiceId: v.string(),
}),
}),
}));
```

### Using ArkType

```ts
// src/shared/navigation.ts
import { createNavigationConfig } from 'next-safe-navigation';
import { type } from 'arktype';

export const { routes, useSafeParams, useSafeSearchParams } =
createNavigationConfig((defineRoute) => ({
customers: defineRoute('/customers', {
search: type({
'query?': "string = ''",
'page?': 'string.numeric.parse = 1',
}),
}),
invoice: defineRoute('/invoices/[invoiceId]', {
params: type({
invoiceId: 'string',
}),
}),
}));
```
Binary file modified bun.lockb
Binary file not shown.
20 changes: 15 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "next-safe-navigation",
"version": "0.3.3",
"author": "Luke Morales <lukemorales@live.com>",
"description": "Type-safe navigation for NextJS App router",
"description": "Type-safe navigation for NextJS App router with Standard Schema support (Zod, Valibot, ArkType, etc.)",
"license": "MIT",
"repository": {
"type": "git",
Expand Down Expand Up @@ -72,12 +72,16 @@
"tsup": "^8.0.2",
"typescript": "^4.8.2",
"vitest": "^1.4.0",
"zod": "^3.22.4"
"zod": "^3.25.67"
},
"peerDependencies": {
"typescript": ">=4.8.2",
"next": ">=13.0.0",
"zod": ">=3.20.0"
"typescript": ">=4.8.2"
},
"peerDependenciesMeta": {
"zod": {
"optional": true
}
},
"keywords": [
"next",
Expand All @@ -91,8 +95,14 @@
"type-safety",
"router",
"navigation",
"standard-schema",
"zod",
"valibot",
"arktype",
"runtime validation",
"validation"
]
],
"dependencies": {
"@standard-schema/spec": "^1.0.0"
}
}
Loading