Technical

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

M
Matt
Author
September 03, 2025
6 min read

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:

  1. Look up code in manual (2 min)
  2. Type "079300.01" (10 sec)
  3. Look up class value (1 min)
  4. Type "400" (5 sec)
  5. Total: 3+ minutes

After:

  1. Type "desk" (2 sec)
  2. Click on result (1 sec)
  3. 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:

  1. Understand your data - Test APIs directly, don't rely solely on documentation
  2. Cache aggressively - Performance improvements compound user satisfaction
  3. Sweat the details - Small UX touches (highlighting, conditional interactions) matter
  4. 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

Tags

nmfcapitypescriptnextjsoptimizationsearch