
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
route-hints
Advanced tools
A React component and utilities for suggesting similar routes when users hit 404 pages in Next.js applications using Levenshtein distance
A React component and utilities for suggesting similar routes when users hit 404 pages in Next.js applications using Levenshtein distance algorithm.
npm install route-hints
# or
yarn add route-hints
# or
pnpm add route-hints
You can automatically discover routes from your Next.js app directory:
// scripts/generate-routes.js
const { generateRoutesFile } = require('route-hints');
// Generate routes.json file from your app directory
generateRoutesFile('./app', './public/routes.json');
Add this to your package.json build process:
{
"scripts": {
"build": "node scripts/generate-routes.js && next build"
}
}
// app/not-found.tsx
'use client';
import { useRouteSuggestions } from 'route-hints';
export default function NotFound() {
const { suggestions, isLoading, error, hasNoSuggestions } = useRouteSuggestions(
typeof window !== 'undefined' ? window.location.pathname : '/',
{
threshold: 3,
maxSuggestions: 5
}
);
return (
<div className="not-found-page">
<h1>404 - Page Not Found</h1>
<p>The page you're looking for doesn't exist.</p>
{isLoading && <p>Loading suggestions...</p>}
{error && <p>Error: {error}</p>}
{hasNoSuggestions && <p>No similar pages found.</p>}
{suggestions.length > 0 && (
<div>
<h3>Did you mean one of these pages?</h3>
<ul>
{suggestions.map((suggestion) => (
<li key={suggestion.path}>
<a href={suggestion.path}>
{suggestion.path}
</a>
<small> (similarity: {suggestion.distance})</small>
</li>
))}
</ul>
</div>
)}
</div>
);
}
// app/not-found.tsx
import { RouteData } from 'route-hints';
export default function NotFound() {
return (
<div className="not-found-page">
<h1>404 - Page Not Found</h1>
<p>The page you're looking for doesn't exist.</p>
<RouteData
invalidPath={typeof window !== 'undefined' ? window.location.pathname : '/'}
threshold={3}
maxSuggestions={5}
>
{({ suggestions, isLoading, error, hasNoSuggestions }) => {
if (isLoading) return <p>Loading suggestions...</p>;
if (error) return <p>Error: {error}</p>;
if (hasNoSuggestions) return <p>No similar pages found.</p>;
return (
<div className="my-custom-suggestions">
<h3>Did you mean one of these pages?</h3>
<div className="suggestion-grid">
{suggestions.map((suggestion) => (
<div key={suggestion.path} className="suggestion-card">
<a href={suggestion.path} className="suggestion-link">
{suggestion.path}
</a>
<span className="similarity-score">
Score: {suggestion.distance}
</span>
</div>
))}
</div>
</div>
);
}}
</RouteData>
</div>
);
}
For non-React usage or server-side logic:
import { getSuggestions, collectRoutes } from 'route-hints';
// Collect routes from your app directory
const routes = collectRoutes('./app');
// Get suggestions for an invalid path
const suggestions = getSuggestions('/prodcuts', routes, {
threshold: 2,
maxSuggestions: 3
});
console.log(suggestions);
// Output: [{ path: '/products', distance: 1 }]
useRouteSuggestions(invalidPath, options)The main React hook for getting route suggestions data.
Parameters:
invalidPath: string - The current invalid path that triggered 404options: UseRouteSuggestionsOptions - Configuration optionsReturns:
interface UseRouteSuggestionsReturn {
suggestions: RouteSuggestion[];
isLoading: boolean;
error: string | null;
hasNoSuggestions: boolean;
}
Options:
interface UseRouteSuggestionsOptions {
routes?: string[]; // Optional: Custom list of valid routes
threshold?: number; // Default: 3 - Maximum Levenshtein distance
maxSuggestions?: number; // Default: 5 - Maximum number of suggestions
caseSensitive?: boolean; // Default: false - Case sensitive matching
autoLoad?: boolean; // Default: true - Auto-load suggestions
routesJsonPath?: string; // Default: '/routes.json' - Path to routes file
}
RouteDataA render prop component that provides suggestion data to its children.
Props:
interface RouteDataProps extends UseRouteSuggestionsOptions {
invalidPath: string;
children: (data: UseRouteSuggestionsReturn) => React.ReactNode;
}
SuggestedRoutes (Optional styled component)A pre-styled React component for displaying route suggestions. You can still use this if you want a quick implementation, but we recommend using the data hook or render prop component for full design control.
collectRoutes(appDirPath: string): string[]Scans a Next.js App Router directory and returns all valid route paths.
import { collectRoutes } from 'route-hints';
const routes = collectRoutes('./app');
console.log(routes); // ['/',' '/about', '/products', ...]
generateRoutesFile(appDirPath: string, outputPath: string): voidGenerates a JSON file containing all discovered routes.
import { generateRoutesFile } from 'route-hints';
generateRoutesFile('./app', './public/routes.json');
getSuggestions(invalidPath: string, validRoutes: string[], options?: SuggestionOptions): RouteSuggestion[]Returns all route suggestions within the specified threshold.
import { getSuggestions } from 'route-hints';
const suggestions = getSuggestions('/prodcts', ['/products', '/about'], {
threshold: 2,
caseSensitive: false
});
// Returns: [{ path: '/products', distance: 1 }]
getBestSuggestions(invalidPath: string, validRoutes: string[], maxSuggestions?: number, threshold?: number): RouteSuggestion[]Returns the best route suggestions up to the specified limit.
import { getBestSuggestions } from 'route-hints';
const bestSuggestions = getBestSuggestions('/prodcts', routes, 3, 2);
createRouteMatcher(config: RouteMatcherConfig)Creates a configured route matcher with predefined settings.
import { createRouteMatcher } from 'route-hints';
const matcher = createRouteMatcher({
threshold: 2,
maxSuggestions: 5,
caseSensitive: false,
includeExactMatches: true,
includeStartsWithMatches: true
});
const suggestions = matcher.getSuggestions('/prodcts', routes);
The component uses these CSS classes that you can style:
.route-suggestions {
/* Main container */
}
.route-suggestions__title {
/* Suggestion title */
}
.route-suggestions__list {
/* Suggestions list (ul) */
}
.route-suggestions__item {
/* Individual suggestion (li) */
}
.route-suggestions__link {
/* Suggestion links (a) */
}
.route-suggestions {
padding: 1rem;
border: 1px solid #e1e5e9;
border-radius: 0.5rem;
background-color: #f8f9fa;
}
.route-suggestions__title {
color: #495057;
margin-bottom: 0.5rem;
font-size: 1.1rem;
}
.route-suggestions__list {
list-style: none;
padding: 0;
margin: 0;
}
.route-suggestions__item {
margin-bottom: 0.25rem;
}
.route-suggestions__link {
color: #007bff;
text-decoration: none;
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
display: inline-block;
transition: background-color 0.2s;
}
.route-suggestions__link:hover {
background-color: #e9ecef;
text-decoration: underline;
}
The route discovery mechanism:
app/ folder)page.tsx, page.ts, page.js)(marketing) - don't affect URL structure_components - ignored[id] - included as /[id]app/
├── page.tsx # → /
├── about/
│ └── page.tsx # → /about
├── products/
│ ├── page.tsx # → /products
│ └── [id]/
│ └── page.tsx # → /products/[id]
├── (marketing)/
│ ├── pricing/
│ │ └── page.tsx # → /pricing
│ └── features/
│ └── page.tsx # → /features
└── _components/
└── Header.tsx # Ignored (private folder)
Generated routes: ["/", "/about", "/products", "/products/[id]", "/pricing", "/features"]
For better performance, generate routes at build time:
// next.config.js
const { generateRoutesFile } = require('route-hints');
/** @type {import('next').NextConfig} */
const nextConfig = {
webpack: (config, { isServer, dev }) => {
if (isServer && !dev) {
// Generate routes during production build
generateRoutesFile('./app', './public/routes.json');
}
return config;
},
};
module.exports = nextConfig;
import { getSuggestions, calculateSimilarity } from 'route-hints';
function getSmartSuggestions(invalidPath: string, routes: string[]) {
// Use different thresholds based on path length
const threshold = invalidPath.length <= 5 ? 1 : Math.floor(invalidPath.length * 0.3);
return getSuggestions(invalidPath, routes, threshold);
}
'use client';
import { SuggestedRoutes } from 'route-hints';
import { useEffect } from 'react';
export default function NotFound() {
useEffect(() => {
// Track 404 events
if (typeof window !== 'undefined') {
analytics.track('404_Page_View', {
path: window.location.pathname,
referrer: document.referrer
});
}
}, []);
return (
<div>
<h1>404 - Page Not Found</h1>
<SuggestedRoutes
invalidPath={typeof window !== 'undefined' ? window.location.pathname : '/'}
/>
</div>
);
}
This package includes comprehensive TypeScript definitions:
import type {
RouteSuggestion,
SuggestedRoutesProps
} from 'route-hints';
const suggestion: RouteSuggestion = {
path: '/products',
distance: 1
};
git checkout -b feature/amazing-feature)git commit -m 'Add some amazing feature')git push origin feature/amazing-feature)MIT © [Your Name]
Made with ❤️ for the Next.js community
FAQs
A React component and utilities for suggesting similar routes when users hit 404 pages in Next.js applications using Levenshtein distance
We found that route-hints demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.