npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

next-typed-url

v0.0.10

Published

Generate next-typesafe-url–style, typed URL builders for Next.js projects.

Readme

next-typed-url

Generate next-typesafe-url–style, typed URL builders for Next.js projects.

By default it scans a single Next.js root (./src/pages and/or ./src/app). Pass --apps <dir> to treat that folder as a collection of micro frontends (apps/<app>/(src/)pages, apps/<app>/(src/)app). In either mode it collects static & dynamic routes and emits:

  • builder.ts: the strongly typed buildUrl implementation for each app
  • index.ts under each app directory: an ergonomic wrapper (webUrl, adminUrl, ...)
  • Root-level index.ts: re-exports per-app builders and provides an urls dictionary

The route parameter is a string-literal union, and dynamic segments require a typed params object so IDE autocomplete and type safety work out of the box. Search parameters follow Next.js's searchParams naming for familiarity.

Install

npm i -D next-typed-url
# or
pnpm add -D next-typed-url
# or
yarn add -D next-typed-url

CLI Usage

# run with npx (single Next.js project)
npx next-typed-url --root . --out ./typed-url

# monorepo / micro frontend workspace
npx next-typed-url --apps ./apps --out ./packages/typed-url

# if installed as a devDependency
next-typed-url --root . --out ./typed-url

Options

  • --root <dir>: Next.js project root (default: current working directory)
  • --apps <dir>: treat the directory as a collection of apps (enables multi mode)
  • --app-name <name>: override the inferred name for single mode
  • --out <dir>: output directory for generated files (default: packages/next-typed-url)
  • --exclude-404: exclude 404 routes

Tip: add it to your repository script, e.g.

{
  "scripts": {
    "urlgen": "next-typed-url --root . --out typed-url",
  }
}

Output API

For an app named web, the generator emits:

  • <out>/web/builder.ts (the raw buildUrl implementation and route types)
  • <out>/web/index.ts (exports webUrl with build, href, pathname helpers)
  • <out>/index.ts (re-exports each {app}Url; when a single app is generated urls equals that helper, otherwise it's a dictionary of helpers)
  • WebRoute, WebRouteInput, and WebRouteSearchParamsMap types that capture both static/dynamic segments and search-param typings

Type and function shape (dynamic routes include an accompanying params type):

import type { Query as WebProductQuery } from "../../apps/web/src/pages/product/[productID]";
import type { Query as WebDocsQuery } from "../../apps/web/src/pages/docs/[...slug]";

export type WebRoute =
  | "/"
  | "/account"
  | "/product/[productID]"
  | "/docs/[...slug]";

type RouteParamValue = string | number;
type RouteParamArray = Array<RouteParamValue>;

type WebRouteSearchParamsMap = {
  "/product/[productID]": WebProductQuery;
  "/docs/[...slug]": WebDocsQuery;
};

export type WebRouteInput =
  | { route: "/" }
  | { route: "/account" }
  | {
      route: "/product/[productID]";
      params: { productID: RouteParamValue };
      searchParams?: WebRouteSearchParamsMap["/product/[productID]"];
    }
  | {
      route: "/docs/[...slug]";
      params: { slug: RouteParamArray };
      searchParams?: WebRouteSearchParamsMap["/docs/[...slug]"];
    };

export const buildUrl = (args: WebRouteInput) => {
  const pathname = buildPath(args.route, args.params);
  const search = buildSearchString(args.searchParams);
  return {
    pathname,
    href: search ? `${pathname}${search}` : pathname,
    ...(search ? { search } : {}),
    ...(args.searchParams ? { searchParams: args.searchParams } : {}),
  };
};

Usage example (single project generated with npx next-typed-url --root . --out ./typed-url):

import { urls } from "@/typed-url"; // single mode: urls === webUrl

const url = urls.build({ route: "/account/point" });
// -> { pathname: '/account/point', href: '/account/point' }

const productUrl = urls.build({
  route: "/product/[productID]",
  params: { productID: 23 },
  searchParams: { ref: "campaign-42" },
});
// -> {
//      pathname: '/product/23',
//      href: '/product/23?ref=campaign-42',
//      search: '?ref=campaign-42',
//      searchParams: { ref: 'campaign-42' }
//    }

const docsUrl = urls.build({
  route: "/docs/[...slug]",
  params: { slug: ["guides", "advanced"] },
  searchParams: { section: "utilities" },
});
// -> {
//      pathname: '/docs/guides/advanced',
//      href: '/docs/guides/advanced?section=utilities',
//      search: '?section=utilities',
//      searchParams: { section: 'utilities' }
//    }

// In multi-app mode, `urls` exposes each app namespace.
const adminHref = urls.admin.href({ route: "/users/[id]", params: { id: 99 } });

Route Coverage

  • Included:
    • Pages Router: files under <root>/(src/)pages/** (single mode) or apps/<app>/(src/)pages/** (multi mode) with extensions .tsx, .ts, .jsx, .js (excluding _app, _document, _error, 500, and optionally 404).
    • App Router: files named page.{tsx,ts,jsx,js} or default.* under <root>/(src/)app/** (single) or apps/<app>/(src/)app/** (multi). Route groups (group) and parallel (@slot) directories are ignored when computing URL paths.
  • Excluded: pages/api/**, special pages _app, _document, _error, 500. Use --exclude-404 to also drop 404 in Pages Router mode. App Router-only helpers like loading, error, layout, route.ts are ignored.
  • Dynamic segments ([id], [...slug], [[...slug]]) produce typed params. Catch-all routes expect a non-empty array, optional catch-all routes accept undefined or an array. Index handling remains the same: /foo/index.tsx becomes /foo.
  • Search-parameter types: if you want typed search parameters, export type Query = { ... } (or interface Query) from the page module. The generator detects this export and imports it as the searchParams type for that route.

Query Types

  1. Inside the page module (e.g., apps/web/src/pages/product/[productID].tsx or apps/web/src/app/blog/[slug]/page.tsx) define export type Query = { ... } or export interface Query { ... }. The export name must always be Query.
  2. The generator imports that type with a statement such as import type { Query as xxx_Query } from './page' and wires it into WebRouteSearchParamsMap.
  3. The generator does not provide runtime validation; it only surfaces type-safe autocomplete/checking. If you need runtime guards, implement them in the page module.

Programmatic API

import { generate } from "next-typed-url";

// single project (default)
await generate({
  appDir: ".",
  outDir: "typed-url",
});

// multi-app workspace
await generate({
  appsDir: "apps",
  outDir: "packages/typed-url",
});

Notes

  • Targets the Next.js Pages Router and App Router (route groups/parallel routes are supported via folder semantics).
  • Generated files are deterministic and safe to commit.
  • Requires Node.js. If you build from source TypeScript, ensure @types/node is available.

Development

  • Format: pnpm format
  • Lint: pnpm lint
  • Test: pnpm test