@safe-routes/nextjs
v0.0.12
Published
Type-safe route utilities for Next.js
Maintainers
Readme
@safe-routes/nextjs
Zero-dependency type-safe routing utilities that work seamlessly with Next.js App Router and Pages Router.
Features
- Full TypeScript support
- Zero runtime dependencies
- App Router support
- Dynamic routes ([id])
- Catch-all routes ([...slug])
- Optional catch-all routes ([[...name]])
- SearchParams type inference
- Pages Router support
- Dynamic routes
- Catch-all routes
- Index routes
- Nested routes
- SearchParams type inference
Acknowledgments
This project is inspired by:
- safe-routes - Type-safe routing for React Router apps
Installation
npm install @safe-routes/nextjs --save-dev
# or
yarn add @safe-routes/nextjs --dev
# or
pnpm add @safe-routes/nextjs --dev
# or
npx @safe-routes/nextjsSetup
The CLI will generate type definitions in the .safe-routes directory at the root of your project.
Add the CLI to your package.json scripts:
{
"scripts": {
"dev": "npm run dev:routes & npm run dev:next",
"dev:routes": "safe-routes --watch",
"dev:next": "next dev",
"build": "safe-routes && next build"
}
}You can customize the output directory using the --out-dir option:
- Update your package.json scripts:
{
"scripts": {
"dev:routes": "safe-routes --watch --out-dir ./custom/path"
}
}- Update your tsconfig.json:
{
"compilerOptions": {
"paths": {
"@safe-routes/nextjs": ["./custom/path"]
}
}
}CLI Options
Usage: safe-routes [options]
Options:
-w, --watch Watch for file changes and regenerate types
-o, --out-dir <path> Output directory (default: .safe-routes)
-t, --trailing-slash true | false Enable trailing slash in generated routes (default: true)
-h, --help Display help for commandExamples
# Watch mode with custom output directory
safe-routes --watch --out-dir ./typesUsage
The safeRoute function provides type-safe routing with the following signature:
import { safeRoute } from '@safe-routes/nextjs';
// For dynamic routes
safeRoute(path, params, searchParams?)
// For static routes
safeRoute(path, searchParams?)Search Parameters Behavior
The searchParams argument becomes optional when:
- The
SearchParamstype is not defined in the page component - All properties in
SearchParamsare optional
// Case 1: All optional properties - searchParams is optional
export type SearchParams = {
lang?: string;
page?: number;
};
safeRoute('/about/'); // OK
safeRoute('/about/', { lang: 'en' }); // OK
// Case 2: Has required properties - searchParams is required
export type SearchParams = {
lang: string; // required
page?: number; // optional
};
safeRoute('/about/'); // Error: searchParams is required
safeRoute('/about/', { lang: 'en' }); // OK
// Case 3: No SearchParams type - searchParams is optional
safeRoute('/about/'); // OKExamples
- Static Routes (no parameters):
// Simple route
safeRoute('/about/'); // => /about/
// With search params
export type SearchParams = {
lang?: 'en' | 'ja';
};
safeRoute('/about/', { lang: 'en' }); // => /about/?lang=en- Dynamic Routes:
// Single dynamic parameter
export type SearchParams = {
tab?: 'profile' | 'settings';
};
safeRoute('/users/[id]/', { id: '123' }); // => /users/123/
safeRoute('/users/[id]/', { id: '123' }, { tab: 'profile' }); // => /users/123/?tab=profile
// Multiple dynamic parameters
safeRoute(
'/users/[userId]/posts/[postId]/',
{ userId: '123', postId: '456' }
); // => /users/123/posts/456/- Catch-all Routes:
// Required catch-all
safeRoute(
'/shop/[...categories]/',
{ categories: ['men', 'shoes'] }
); // => /shop/men/shoes/
// Optional catch-all routes are split into two patterns:
// Pattern 1: Base route without parameters
safeRoute('/products/'); // => /products/
// Pattern 2: Route with optional catch-all
// Note: When using this pattern, params are required
safeRoute(
'/products/[[...filters]]/',
{ filters: ['sale', 'winter'] }
); // => /products/sale/winter/
// This will cause a type error:
safeRoute('/products/[[...filters]]/'); // Error: params are required when specified- Route Groups:
// Route groups are ignored in the path
export type SearchParams = {
redirect?: string;
};
safeRoute('/login/', { redirect: '/dashboard' }); // => /login/?redirect=/dashboardLicense
MIT
Contributing
Contributions are welcome! Please open an issue or submit a pull request.
