0.0.5 • Published 4 months ago
next-typesafe-path v0.0.5
next-typesafe-path
A library for type-safe routing in Next.js.
Features
- Type-safe routing for Next.js
- Support for dynamic routes (
[id]
), catch-all routes ([...slug]
), and optional catch-all routes ([[...slug]]
) - Type-safe handling of search parameters (query parameters)
- Define globally available search parameters
- Compatible with both App Router and Pages Router
Acknowledgments
This project is inspired by:
- safe-routes - Type-safe routing for React Router apps
Installation
npm install next-typesafe-path --save-dev
# or
yarn add next-typesafe-path --dev
# or
pnpm add next-typesafe-path --save-dev
# or
npx next-typesafe-path
Usage
1. Generate Type Definitions
Run the following command to generate type definitions:
npx next-typesafe-path
Add the CLI to your package.json scripts:
{
"scripts": {
"dev": "npm run dev:path & npm run dev:next",
"dev:path": "next-typesafe-path --watch",
"dev:next": "next dev",
"build": "next-typesafe-path && next build"
}
}
generate a _next-typesafe-path.d.ts
file in the root of your project.
Options
--config-dir
: Specify the configuration file directory (default: project root)--trailing-slash
: Add trailing slashes to URLs (default:false
)--watch
: Watch for file changes and regenerate type definitions automatically
2. Set Up Global Search Parameters (Optional)
Create a next-typesafe-path.config.ts
file in the root of your project to define globally available search parameters:
// next-typesafe-path.config.ts
import { createSearchParams, InferSearchParams, setGlobalSearchParams } from "next-typesafe-path";
export const searchParams = createSearchParams((p) => ({
// Define your global search parameters here
locale: p.enumOr(["en", "ja"], "ja").optional(),
}));
export type SearchParams = InferSearchParams<typeof searchParams>;
// Set global configuration
setGlobalSearchParams(searchParams);
Next, import this configuration file in your layout.tsx
(for App Router) or _app.tsx
(for Pages Router):
// app/layout.tsx (App Router)
import "../next-typesafe-path.config";
export default function RootLayout({ children }) {
return (
<html>
<body>{children}</body>
</html>
);
}
// pages/_app.tsx (Pages Router)
import "../next-typesafe-path.config";
import type { AppProps } from "next/app";
export default function MyApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
}
3. Use Routing
Generate Paths
Use the $path
function to generate type-safe paths:
import { $path } from "next-typesafe-path";
// Regular path
const path1 = $path("/about");
// Dynamic route
const path2 = $path("/users/[id]", { id: "123" });
// Path with search parameters
const path3 = $path("/blog", { page: 1, sort: "desc" });
// Dynamic route + search parameters
const path4 = $path("/users/[id]", { id: "123" }, { tab: "profile" });
// Catch-all route
const path5 = $path("/shop/[...categories]", { categories: ["men", "shoes"] });
// Optional catch-all route
const path6 = $path("/products/[[...filters]]", { filters: ["sort", "price"] });
// Optional catch-all route without path param
const path7 = $path("/products/");
Define and Use Search Parameters
Define search parameters in your page component and use them in a type-safe way:
// app/blog/page.tsx (App Router)
import { createSearchParams, InferSearchParams, parseSearchParams } from "next-typesafe-path";
// Define search parameters
const SearchParams = createSearchParams((p) => ({
page: p.numberOr(1),
sort: p.enumOr(["asc", "desc"] as const, "asc"),
q: p.stringOr(""),
}));
// Extract type
export type SearchParams = InferSearchParams<typeof SearchParams>;
// Page component
export default async function BlogPage({
searchParams,
}: {
searchParams: Promise<SearchParams>;
}) {
// Parse search parameters
const params = parseSearchParams(SearchParams, await searchParams);
return (
<div>
<h1>Blog</h1>
<p>Page: {params.page}</p>
<p>Sort: {params.sort}</p>
<p>Query: {params.q}</p>
{/* Access global search parameters */}
<p>Locale: {params.locale}</p>
</div>
);
}
// pages/blog.tsx (Pages Router)
import { createSearchParams, InferSearchParams, parseSearchParams } from "next-typesafe-path";
import { GetServerSideProps } from "next";
// Define search parameters
const SearchParams = createSearchParams((p) => ({
page: p.numberOr(1),
sort: p.enumOr(["asc", "desc"] as const, "asc"),
q: p.stringOr(""),
}));
// Extract type
export type SearchParams = InferSearchParams<typeof SearchParams>;
// Page component
export default function BlogPage({ params }: { params: SearchParams }) {
return (
<div>
<h1>Blog</h1>
<p>Page: {params.page}</p>
<p>Sort: {params.sort}</p>
<p>Query: {params.q}</p>
{/* Access global search parameters */}
<p>Locale: {params.locale}</p>
</div>
);
}
// Parse search parameters on the server side
export const getServerSideProps: GetServerSideProps = async ({ query }) => {
const params = parseSearchParams(SearchParams, query);
return { props: { params } };
};
Advanced Usage
Customize Parameter Builder
Use the createSearchParams
function to create your own parameter builder:
import { createSearchParams, InferSearchParams } from "next-typesafe-path";
// Custom parameter builder
const SearchParams = createSearchParams((p) => ({
// Required parameter
id: p.string(),
// Parameter with default value
page: p.numberOr(1),
// Enum parameter
sort: p.enumOr(["asc", "desc"] as const, "asc"),
// Optional parameter
q: p.string().optional(),
// Array parameter
tags: p.array(p.string()).optional(),
}));
// or
const SearchParams = createSearchParams(() => ({
id: z.string(),
page: z.number(),
}));
// Extract type
export type SearchParams = InferSearchParams<typeof SearchParams>;
License
MIT