Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
> MUI 7 Breadcrumbs with **Next.js Link** support, optional **URL-based generation**, and **built-in SEO microdata**. Router-agnostic with zero runtime deps.

| Item | Value | Badge |
| --------------- | ---------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| --------------- | ---------------------------------------------------------------------------- |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **GitHub repo** | [github.com/ameshkin/nextcrumbs](https://github.com/ameshkin/nextcrumbs) | — |
| **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/install.yml?branch=main\&label=install%20test)](https://github.com/ameshkin/nextcrumbs/actions/workflows/install.yml) |
| **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) |
| **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) |
Expand Down
22 changes: 14 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@ameshkin/nextcrumbs",
"version": "0.1.0",
"version": "0.1.2",
"description": "MUI 7 breadcrumbs with Next.js Link support, optional URL auto-generation, and SEO microdata.",
"license": "MIT",
"type": "module",
Expand Down Expand Up @@ -60,6 +60,6 @@
"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"],
"keywords": ["mui", "breadcrumbs", "nextjs", "react", "material-ui", "seo", "schema.org", "app router", "react router"],
"publishConfig": { "access": "public" }
}
2 changes: 1 addition & 1 deletion src/Breadcrumbs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export default function Breadcrumbs({
}) => (
<Box component="span" sx={sxJoin(defaultContentSx, contentSx)} title={title || label}>
{icon && <Box component="span" sx={iconSx}>{icon}</Box>}
<Box component="span" sx={labelSx}>{label}</Box>
<Typography component="span" sx={labelSx}>{label}</Typography>
</Box>
);

Expand Down
57 changes: 56 additions & 1 deletion src/hooks/useReactRouterBreadcrumbs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,61 @@
*/
import * as React from "react"
import type { ReactNode } from "react"
import { useLocation } from "react-router-dom"

// Lazy import pattern to avoid requiring react-router-dom when not used
// We use a function that accesses the module at runtime to avoid
// webpack trying to resolve it at build time when it's not installed
let useLocationHook: (() => { pathname: string }) | null = null

function getUseLocation(): () => { pathname: string } {
if (useLocationHook) return useLocationHook

// Access react-router-dom dynamically using a pattern that's harder
// for webpack to statically analyze. We construct the module name
// in a way that makes it less obvious to static analysis.
try {
// Split the module name to make it harder for webpack to detect
const parts = ["react", "router", "dom"]
const mod = parts.join("-")

// Try to access the module - this will work if it's available
// @ts-ignore - accessing optional dependency dynamically
let routerModule: any = null

// Try CommonJS require (for Node.js/CommonJS environments)
if (typeof require !== 'undefined') {
try {
routerModule = require(mod)
} catch {
// require failed, module not available
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Synchronous require in ESM module fails at runtime

The code attempts to use require() in an ESM module (package.json has "type": "module"). The require function is not available in ESM contexts or browser environments, causing this code path to always fail. The fallback logic at line 48 will always throw an error when react-router-dom isn't statically imported, making the optional peer dependency non-functional.

Fix in Cursor Fix in Web

}

// For ESM environments, the module should be available through
// normal module resolution if installed. Since we can't use
// dynamic import() synchronously, we rely on the consuming
// project to have it available or configure webpack appropriately.
if (!routerModule) {
throw new Error("react-router-dom not available")
}

if (!routerModule.useLocation || typeof routerModule.useLocation !== 'function') {
throw new Error("useLocation not found in react-router-dom")
}

const hook = routerModule.useLocation
useLocationHook = hook
return hook
} catch (err: any) {
// If react-router-dom is not available, throw a helpful error
throw new Error(
"useReactRouterBreadcrumbs requires 'react-router-dom' to be installed. " +
"Please install it: npm install react-router-dom. " +
`If you're using Next.js and don't need react-router-dom, you can configure ` +
`webpack to ignore it or install it as a dev dependency.`
)
}
}

export type BreadcrumbItem = {
label: string
Expand All @@ -30,6 +84,7 @@ export type ReactRouterBreadcrumbsOptions = {
}

export function useReactRouterBreadcrumbs(options?: ReactRouterBreadcrumbsOptions): BreadcrumbItem[] {
const useLocation = getUseLocation()
const { pathname } = useLocation()
const { rootLabel, basePath, decode = true, exclude = [], mapSegmentLabel } = options || {}

Expand Down