From 44a39bbc6c76561095560aec257d0f5880d134be Mon Sep 17 00:00:00 2001 From: Amir Meshkin Date: Mon, 24 Nov 2025 08:56:36 -0500 Subject: [PATCH 1/8] adding most recent changes. --- README.md | 64 ++++++++++++++++++++++++++++++++++++----------- package-lock.json | 22 ++++++++-------- package.json | 49 ++++++++++++++++++++++++++++-------- 3 files changed, 100 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 7db3e88..829eecd 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ | **CI** | `.github/workflows/ci.yml` | [![CI — main](https://img.shields.io/github/actions/workflow/status/ameshkin/nextcrumbs/ci.yml?branch=main\&label=ci%20\(main\))](https://github.com/ameshkin/nextcrumbs/actions/workflows/ci.yml) [![CI — dev](https://img.shields.io/github/actions/workflow/status/ameshkin/nextcrumbs/ci.yml?branch=dev\&label=ci%20\(dev\))](https://github.com/ameshkin/nextcrumbs/actions/workflows/ci.yml) | | **npm package** | [@ameshkin/nextcrumbs](https://www.npmjs.com/package/@ameshkin/nextcrumbs) | [![npm](https://img.shields.io/npm/v/@ameshkin/nextcrumbs.svg)](https://www.npmjs.com/package/@ameshkin/nextcrumbs) | | **Install** | `npm i @ameshkin/nextcrumbs` | [![install test](https://img.shields.io/github/actions/workflow/status/ameshkin/nextcrumbs/ci-main.yml?branch=main\&label=install%20test)](https://github.com/ameshkin/nextcrumbs/actions/workflows/ci-main.yml) | -| **Peer deps** | `@mui/material@^7`, `@mui/icons-material@^7`, `react@>=18`, `react-dom@>=18` | ![peer deps](https://img.shields.io/badge/peer_deps-MUI%207%20%7C%20Icons%207%20%7C%20React%2018-blue) | +| **Peer deps** | `@mui/material@^7`, `@mui/icons-material@^7`, `react@>=18`, `react-dom@>=18`
(Optional: `react-router-dom@>=6`) | ![peer deps](https://img.shields.io/badge/peer_deps-MUI%207%20%7C%20Icons%207%20%7C%20React%2018-blue) | | **Types** | TypeScript | ![types](https://img.shields.io/badge/types-TypeScript-blue.svg) | | **License** | MIT | [![license](https://img.shields.io/badge/license-MIT-black.svg)](LICENSE) | | **Bundle size** | bundlephobia | [![bundle size](https://img.shields.io/bundlephobia/minzip/%40ameshkin%2Fnextcrumbs?label=bundle%20size)](https://bundlephobia.com/package/@ameshkin/nextcrumbs) | @@ -28,6 +28,7 @@ * [Icon & Separator Examples](#icon--separator-examples) * [Accessibility & SEO](#accessibility--seo) * [Dependency Checks](#dependency-checks) +* [Troubleshooting](#troubleshooting) * [FAQ](#faq) * [Contributing](#contributing) * [License](#license) @@ -60,10 +61,14 @@ npm i @ameshkin/nextcrumbs # peer deps npm i @mui/material @mui/icons-material react react-dom -# optional for React Router examples +# optional: only needed if using useReactRouterBreadcrumbs hook npm i react-router-dom ``` +> **Note for Next.js projects:** If you're using Next.js and don't need React Router, you can either: +> - Install `react-router-dom` as a dev dependency: `npm i -D react-router-dom` +> - Or configure webpack to ignore it (see [Troubleshooting](#troubleshooting)) + --- ## Quick Start (Next.js) @@ -119,8 +124,7 @@ See full guide: [`docs/react-router.md`](./docs/react-router.md) ```tsx import { MemoryRouter, Routes, Route } from "react-router-dom"; -import { Breadcrumbs } from "@ameshkin/nextcrumbs"; -import useReactRouterBreadcrumbs from "@ameshkin/nextcrumbs"; +import { Breadcrumbs, useReactRouterBreadcrumbs } from "@ameshkin/nextcrumbs"; function Trail() { const items = useReactRouterBreadcrumbs({ @@ -142,7 +146,7 @@ export default function Demo() { } ``` -> `react-router-dom` is an optional peer. Install it only if you use the router hook. +> **Important:** `react-router-dom` is an **optional peer dependency**. You only need to install it if you use the `useReactRouterBreadcrumbs` hook. The package uses lazy loading, so importing other parts of the library won't require `react-router-dom` to be installed. --- @@ -156,8 +160,7 @@ export default function Demo() { | `LinkComponent` | `React.ElementType` | `@mui/material/Link` | Custom link component (e.g., Next.js `Link`). | | `muiProps` | `Omit` | — | Props passed through to MUI ``. | | `separatorIcon` | `React.ReactNode` | `ChevronRightIcon` | Icon/node placed between items. | -| `homeLabel` | `string` | `"Dashboard"` | If a crumb’s label matches, it can receive a Home icon. | -| `withSchema` | `boolean` | `true` | Adds `schema.org/BreadcrumbList` microdata to the markup. | +| `homeLabel` | `string` | `"Home"` | If a crumb's label matches, it can receive a Home icon. | ### `usePathBreadcrumbs(pathname, options?)` @@ -203,9 +206,9 @@ import { Breadcrumbs } from "@ameshkin/nextcrumbs"; ## Accessibility & SEO -* Uses MUI’s accessible ``. -* **SEO microdata is enabled by default** via `withSchema`. Set `withSchema={false}` to disable. +* Uses MUI's accessible ``. * Minimal DOM footprint; sensible defaults for current page vs. links. +* Follows ARIA best practices for breadcrumb navigation. > Prefer **page-level JSON-LD** for static SEO markup. See: [`docs/vanilla-json.md`](./docs/vanilla-json.md) @@ -215,22 +218,55 @@ import { Breadcrumbs } from "@ameshkin/nextcrumbs"; ```bash npm ls @mui/material @mui/icons-material react react-dom -# optional router +# optional router (only needed if using useReactRouterBreadcrumbs) npm ls react-router-dom ``` +## Troubleshooting + +### Webpack Error: "Can't resolve 'react-router-dom'" + +If you're using Next.js (or another webpack-based bundler) and see this error even though you're not using the React Router hook, you have two options: + +**Option 1: Install as dev dependency (Recommended)** +```bash +npm install --save-dev react-router-dom +``` + +**Option 2: Configure webpack to ignore it** + +For Next.js, add this to your `next.config.js`: + +```js +/** @type {import('next').NextConfig} */ +const nextConfig = { + webpack: (config) => { + config.externals = config.externals || {} + config.externals['react-router-dom'] = 'commonjs react-router-dom' + return config + } +} + +module.exports = nextConfig +``` + +> **Note:** The `useReactRouterBreadcrumbs` hook uses lazy loading, so `react-router-dom` is only accessed when the hook is actually called. However, webpack may still try to resolve it during static analysis. The solutions above prevent webpack from failing when the module isn't installed. + --- ## FAQ **Does it work with React Router?** -Yes. Use `useReactRouterBreadcrumbs()` or pass your router’s `` via `LinkComponent`. +Yes. Use `useReactRouterBreadcrumbs()` or pass your router's `` via `LinkComponent`. Note that `react-router-dom` is optional and only needed if you use the hook. + +**Do I need to install react-router-dom if I'm using Next.js?** +No, you only need it if you're using the `useReactRouterBreadcrumbs` hook. If webpack complains about it, install it as a dev dependency or configure webpack to ignore it (see [Troubleshooting](#troubleshooting)). -**How do I change the last crumb’s style?** -The last item usually has no `href`. Style it as the current page using your theme or conditional props. +**How do I change the last crumb's style?** +The last item usually has no `href`. Style it as the current page using your theme or conditional props via the `currentSx` prop. **Can I disable icons entirely?** -Yes—don’t pass `icon`, and set `homeLabel` to a non-matching value. +Yes—don't pass `icon` in your items, and set `homeLabel` to a non-matching value if you don't want the home icon. --- diff --git a/package-lock.json b/package-lock.json index 687e2a9..f275a25 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "jsdom": "^22.1.0", "react": "^18.3.1", "react-dom": "^18.3.1", - "react-router-dom": "^7.9.5", + "react-router-dom": "^7.9.6", "tsup": "^8.5.0", "typescript": "^5.9.3", "vitest": "^4.0.6" @@ -2306,9 +2306,9 @@ } }, "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, "dependencies": { "foreground-child": "^3.1.0", @@ -3056,9 +3056,9 @@ "peer": true }, "node_modules/react-router": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.5.tgz", - "integrity": "sha512-JmxqrnBZ6E9hWmf02jzNn9Jm3UqyeimyiwzD69NjxGySG6lIz/1LVPsoTCwN7NBX2XjCEa1LIX5EMz1j2b6u6A==", + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.6.tgz", + "integrity": "sha512-Y1tUp8clYRXpfPITyuifmSoE2vncSME18uVLgaqyxh9H35JWpIfzHo+9y3Fzh5odk/jxPW29IgLgzcdwxGqyNA==", "dev": true, "dependencies": { "cookie": "^1.0.1", @@ -3078,12 +3078,12 @@ } }, "node_modules/react-router-dom": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.9.5.tgz", - "integrity": "sha512-mkEmq/K8tKN63Ae2M7Xgz3c9l9YNbY+NHH6NNeUmLA3kDkhKXRsNb/ZpxaEunvGo2/3YXdk5EJU3Hxp3ocaBPw==", + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.9.6.tgz", + "integrity": "sha512-2MkC2XSXq6HjGcihnx1s0DBWQETI4mlis4Ux7YTLvP67xnGxCvq+BcCQSO81qQHVUTM1V53tl4iVVaY5sReCOA==", "dev": true, "dependencies": { - "react-router": "7.9.5" + "react-router": "7.9.6" }, "engines": { "node": ">=20.0.0" diff --git a/package.json b/package.json index 5a61805..24787b0 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,11 @@ "default": "./dist/index.js" } }, - "files": ["dist", "README.md", "LICENSE"], + "files": [ + "dist", + "README.md", + "LICENSE" + ], "sideEffects": false, "scripts": { "clean": "tsup --clean", @@ -36,9 +40,15 @@ "react-router-dom": ">=6 || >=7" }, "peerDependenciesMeta": { - "@emotion/react": { "optional": false }, - "@emotion/styled": { "optional": false }, - "react-router-dom": { "optional": true } + "@emotion/react": { + "optional": false + }, + "@emotion/styled": { + "optional": false + }, + "react-router-dom": { + "optional": true + } }, "devDependencies": { "@testing-library/jest-dom": "^6.9.1", @@ -46,7 +56,7 @@ "jsdom": "^22.1.0", "react": "^18.3.1", "react-dom": "^18.3.1", - "react-router-dom": "^7.9.5", + "react-router-dom": "^7.9.6", "tsup": "^8.5.0", "typescript": "^5.9.3", "vitest": "^4.0.6" @@ -55,11 +65,30 @@ "type": "git", "url": "git+https://github.com/ameshkin/nextcrumbs.git" }, - "bugs": { "url": "https://github.com/ameshkin/nextcrumbs/issues" }, + "bugs": { + "url": "https://github.com/ameshkin/nextcrumbs/issues" + }, "homepage": "https://github.com/ameshkin/nextcrumbs#readme", - "funding": { "type": "github", "url": "https://github.com/sponsors/ameshkin" }, - "engines": { "node": ">=20.19 <21 || >=22.12" }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ameshkin" + }, + "engines": { + "node": ">=20.19 <21 || >=22.12" + }, "packageManager": "npm@10.5.0", - "keywords": ["mui", "breadcrumbs", "nextjs", "react", "material-ui", "seo", "schema.org", "app router", "react router"], - "publishConfig": { "access": "public" } + "keywords": [ + "mui", + "breadcrumbs", + "nextjs", + "react", + "material-ui", + "seo", + "schema.org", + "app router", + "react router" + ], + "publishConfig": { + "access": "public" + } } From 4622951c5113faa7dcf9b4d8d9afb995ece770d7 Mon Sep 17 00:00:00 2001 From: Amir Meshkin Date: Mon, 24 Nov 2025 08:56:49 -0500 Subject: [PATCH 2/8] chore(release): 0.1.3 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index f275a25..e14eb73 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@ameshkin/nextcrumbs", - "version": "0.1.2", + "version": "0.1.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@ameshkin/nextcrumbs", - "version": "0.1.2", + "version": "0.1.3", "license": "MIT", "devDependencies": { "@testing-library/jest-dom": "^6.9.1", diff --git a/package.json b/package.json index 24787b0..480b0f4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ameshkin/nextcrumbs", - "version": "0.1.2", + "version": "0.1.3", "description": "MUI 7 breadcrumbs with Next.js Link support, optional URL auto-generation, and SEO microdata.", "license": "MIT", "type": "module", From f5a356c1916cc1f2535c419217a54105d4d5db0a Mon Sep 17 00:00:00 2001 From: Amir Meshkin Date: Mon, 24 Nov 2025 09:28:06 -0500 Subject: [PATCH 3/8] feat: add rootLabel option, JSON-LD utilities, and comprehensive JSDoc comments --- README.md | 55 ++++++++-- docs/next-router.md | 3 +- docs/vanilla-json.md | 10 +- src/Breadcrumbs.test.tsx | 65 ++++++++++- src/Breadcrumbs.tsx | 50 +++++++++ src/hooks/usePathBreadcrumbs.test.ts | 26 +++++ src/hooks/usePathBreadcrumbs.ts | 42 ++++--- src/hooks/useReactRouterBreadcrumbs.ts | 58 ++++++++-- src/index.ts | 5 +- src/utils/jsonld.test.tsx | 146 +++++++++++++++++++++++++ src/utils/jsonld.ts | 144 ++++++++++++++++++++++++ tsup.config.ts | 2 +- 12 files changed, 561 insertions(+), 45 deletions(-) create mode 100644 src/utils/jsonld.test.tsx create mode 100644 src/utils/jsonld.ts diff --git a/README.md b/README.md index 829eecd..7cf562b 100644 --- a/README.md +++ b/README.md @@ -40,8 +40,8 @@ * ✅ **MUI** Breadcrumbs under the hood (accessible, stable) * ✅ **Next.js** ready via pluggable `LinkComponent` (e.g., `next/link`) * ✅ Optional **URL → items** helper for App Router (`usePathBreadcrumbs`) -* ✅ **React Router** helper hook (`useReactRouterBreadcrumbs`) -* ✅ Built-in **schema.org/BreadcrumbList** microdata via `withSchema` +* ✅ **React Router** helper hook (`useReactRouterBreadcrumbs`) - optional dependency +* ✅ Fully typed with TypeScript --- @@ -108,6 +108,7 @@ export default function AutoTrail() { const pathname = usePathname(); const items = usePathBreadcrumbs(pathname, { baseHref: "/", + rootLabel: "Home", labelMap: { new: "Create" }, exclude: ["_private"] }); @@ -164,12 +165,14 @@ export default function Demo() { ### `usePathBreadcrumbs(pathname, options?)` -| Option | Type | Default | Description | -| ---------- | ----------------------- | ------- | ---------------------------------- | -| `baseHref` | `string` | `"/"` | Root href for the first crumb. | -| `labelMap` | `Record` | `{}` | Override labels by URL segment. | -| `exclude` | `string[]` | `[]` | Skip specific segments. | -| `decode` | `boolean` | `true` | `decodeURIComponent` each segment. | +| Option | Type | Default | Description | +| -------------- | ----------------------- | ------------ | ---------------------------------- | +| `baseHref` | `string` | `"/"` | Root href for the first crumb. | +| `labelMap` | `Record` | `{}` | Override labels by URL segment. | +| `exclude` | `string[]` | `[]` | Skip specific segments. | +| `decode` | `boolean` | `true` | `decodeURIComponent` each segment. | +| `rootLabel` | `string` | `"Dashboard"`| Label for the root/home breadcrumb. | +| `transformLabel` | `(segment: string) => string` | — | Custom label formatter function. | ### `useReactRouterBreadcrumbs(options?)` @@ -210,7 +213,41 @@ import { Breadcrumbs } from "@ameshkin/nextcrumbs"; * Minimal DOM footprint; sensible defaults for current page vs. links. * Follows ARIA best practices for breadcrumb navigation. -> Prefer **page-level JSON-LD** for static SEO markup. See: [`docs/vanilla-json.md`](./docs/vanilla-json.md) +### JSON-LD Structured Data + +For SEO, you can add JSON-LD structured data using the built-in utilities: + +```tsx +import { Breadcrumbs, BreadcrumbJsonLd, breadcrumbsToJsonLd } from "@ameshkin/nextcrumbs"; + +function MyPage() { + const items = [ + { label: "Home", href: "/" }, + { label: "Products", href: "/products" }, + { label: "New Product" } + ]; + + return ( + <> + + + + ); +} +``` + +Or use the `toJsonLd` function for custom JSON-LD generation: + +```tsx +import { toJsonLd } from "@ameshkin/nextcrumbs"; + +const jsonLd = toJsonLd( + [{ name: "Home", href: "/" }, { name: "Products" }], + { origin: "https://example.com" } +); +``` + +> See [`docs/vanilla-json.md`](./docs/vanilla-json.md) for more examples. --- diff --git a/docs/next-router.md b/docs/next-router.md index 46b66cd..ea056c0 100644 --- a/docs/next-router.md +++ b/docs/next-router.md @@ -105,8 +105,7 @@ type PathOptions = { ## Accessibility & SEO * `` for screen readers -* `withSchema` is enabled by default to add `schema.org/BreadcrumbList` -* For JSON-LD, add page-level `