0.0.5 • Published 4 months ago

next-typesafe-path v0.0.5

Weekly downloads
-
License
MIT
Repository
github
Last release
4 months ago

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:

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

0.0.5

4 months ago

0.0.4

4 months ago

0.0.3

4 months ago

0.0.2

4 months ago