Building a Modern NMFC Search Feature: From Manual Entry to Smart Auto-Population
How we transformed a tedious freight classification process into a delightful user experience with Next.js, TypeScript, and smart API integration
Building a Modern NMFC Search Feature: From Manual Entry to Smart Auto-Population
How we transformed a tedious freight classification process into a delightful user experience with Next.js, TypeScript, and smart API integration
📋 Table of Contents
The Problem
In the freight logistics industry, NMFC (National Motor Freight Classification) codes are essential for accurate shipping quotes. These codes determine freight classes, which directly impact shipping costs. However, the traditional process is painful:
- Manual Entry: Users must memorize or look up 6-digit codes
- Error-Prone: One wrong digit can lead to incorrect pricing
- Complex Hierarchy: Codes have sub-classifications based on density (e.g., 079300.01 for items less than 1 lb/cu ft)
- No Context: Users don't see descriptions or class values while entering codes
Real-World Example
Imagine shipping a desk. The NMFC code 079300 covers furniture, but it has 11 sub-classifications (079300.01 through 079300.11) with freight classes ranging from 60 to 400. Entering this manually is a nightmare.
The Solution
We built an intelligent NMFC search feature that transforms this painful process into a smooth, error-free experience.
Key Features
🔍 Smart Search with Real-Time API Integration
// Integration with FreightToolkit API
const response = await fetch(
`https://freighttoolkit.com/api/v1/nmfc/search?q=${searchTerm}`,
{
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
}
}
)
📊 Hierarchical Table Display
Instead of a flat list, we display parent-child relationships clearly:
NMFC Description Class
─────────────────────────────────────────────────────────────────
079300.00 Furniture or Furniture Parts, viz... -
079300.01 Less than 1 400
079300.02 1 but less than 2 300
079300.03 2 but less than 4 250
⚡ Auto-Population of Multiple Fields
When users select an item, we intelligently split and populate three separate fields:
// Smart field population
const handleNMFCSelect = (result, subItem) => {
// Split 079300.01 into components
const articleId = result.article_id; // → 079300
const articleSub = result.article_sub; // → 01
const classValue = result.class; // → 400
// Populate individual form fields
handlePackageItemChange(itemId, 'nmfc', articleId);
handlePackageItemChange(itemId, 'sub', articleSub);
handlePackageItemChange(itemId, 'freightClass', classValue);
}
Technical Implementation
1. Understanding the API Response Structure
The FreightToolkit API had an undocumented quirk - sub-items weren't in a sub
property but in an articles
array:
{
"data": [
{
"article_id": 79300,
"article_sub": 0,
"description": "Furniture or Furniture Parts...",
"articles": [ // Sub-items are here!
{
"article_id": 79300,
"article_sub": 1,
"class": "400",
"description": "Less than 1"
}
]
}
]
}
2. Data Processing Pipeline
We created a processing function to transform the flat API response into a hierarchical structure:
const processHierarchicalData = (items: NMFCApiItem[]): NMFCProcessedItem[] => {
const result: NMFCProcessedItem[] = []
items.forEach(item => {
const processedItem: NMFCProcessedItem = { ...item }
// Convert 'articles' array to 'sub' for consistent handling
if (item.articles && Array.isArray(item.articles)) {
processedItem.sub = item.articles
delete processedItem.articles
}
result.push(processedItem)
})
return result
}
3. Visual Enhancements
Keyword Highlighting
We implemented regex-based highlighting to make search results scannable:
const highlightKeywords = (text: string, searchTerm: string): React.ReactElement => {
const regex = new RegExp(
`(${searchTerm.trim().replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`,
'gi'
);
const parts = text.split(regex);
return (
<>
{parts.map((part, index) => (
regex.test(part) ? (
<span className="text-red-600 font-medium bg-red-50 px-1 rounded">
{part}
</span>
) : (
<span>{part}</span>
)
))}
</>
);
};
Conditional Interactivity
Only rows with class values are clickable:
<tr
className={`${
result.class
? 'hover:bg-gray-50 cursor-pointer transition-colors'
: 'bg-gray-50/50 cursor-not-allowed'
}`}
onClick={() => result.class && handleNMFCSelect(result)}
>
Performance Optimization
The 20x Speed Improvement
Initial API calls took 4 seconds due to database lookups on every request. We implemented intelligent caching:
// In-memory cache with TTL
const USER_CACHE = new Map<string, CachedUser>();
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
function getCachedUser(userId: string): AuthenticatedUser | null {
const cached = USER_CACHE.get(userId);
if (!cached) return null;
const now = Date.now();
if (now - cached.timestamp > cached.ttl) {
USER_CACHE.delete(userId);
return null;
}
return cached.user;
}
Results:
- First query: 4000ms
- Subsequent queries: <200ms
- 20x performance improvement for cached requests
Lessons Learned
1. API Response Structures Can Surprise You
What we expected:
{ "sub": [...] } // Nope!
What we got:
{ "articles": [...] } // Surprise!
2. User Experience Details Matter
Small touches made a big difference:
- Removing unnecessary symbols (↳) for cleaner display
- Proper field separation for backend compatibility
- Visual feedback for clickable vs non-clickable rows
3. Performance Is a Feature
The 4-second wait was unacceptable. Caching transformed the feature from "tolerable" to "delightful."
4. TypeScript Saved Us
Strict typing caught numerous issues during development:
// TypeScript caught this error immediately
interface NMFCApiItem {
article_id?: number;
article_sub?: number;
class?: string;
articles?: NMFCApiItem[]; // This was the key!
}
Results
Before vs After
Aspect | Before | After |
---|---|---|
Data Entry | Manual 6-digit codes | Search and click |
Error Rate | High (typos common) | Near zero |
Time to Enter | 30-60 seconds | 3-5 seconds |
User Satisfaction | Frustrated | Delighted |
API Response Time | N/A | 200ms (cached) |
Real User Scenario
Task: Enter NMFC code for shipping a desk
Before:
- Look up code in manual (2 min)
- Type "079300.01" (10 sec)
- Look up class value (1 min)
- Type "400" (5 sec)
- Total: 3+ minutes
After:
- Type "desk" (2 sec)
- Click on result (1 sec)
- Total: 3 seconds
That's a 60x improvement in task completion time!
Code Repository Structure
/app/quotes/page.tsx # Main quotes page with NMFC search
/app/api/nmfc/search/route.ts # API route for FreightToolkit integration
/lib/auth-middleware.ts # Caching implementation
/scripts/test-nmfc-api.js # Testing utilities
Conclusion
What started as a simple "add search functionality" request evolved into a comprehensive UX overhaul that fundamentally changed how users interact with NMFC codes.
The key takeaways:
- Understand your data - Test APIs directly, don't rely solely on documentation
- Cache aggressively - Performance improvements compound user satisfaction
- Sweat the details - Small UX touches (highlighting, conditional interactions) matter
- Use TypeScript - It catches errors before your users do
The freight industry deserves modern, delightful software. One search box at a time, we're making that happen.
Tech Stack
- Framework: Next.js 15.4.5
- Language: TypeScript
- Styling: Tailwind CSS v4
- UI Components: shadcn/ui
- API Integration: FreightToolkit API
- State Management: React Hooks
What's Next?
- Offline mode with cached NMFC codes
- Fuzzy search for better matching
- Bulk NMFC code import/export
- Historical search suggestions
- API rate limiting and request batching
Have questions or want to discuss freight tech? Find me on Twitter .
#FreightTech #Logistics #WebDevelopment #TypeScript #NextJS #DeveloperExperience