How I Built a Modern Enterprise ERP with Next.js & TypeScript
AlgoBiz · 2026
June 20, 2026 · 15 min read
How I Built a Modern Enterprise ERP with Next.js & TypeScript
A complete technical guide to building a scalable, high-performance modular ERP with Next.js App Router, Zustand, and TypeScript, including optimal folder structures, TanStack Query API integration, Shadcn UI components, Framer Motion layouts, and advanced SEO setups.
Building an enterprise resource planning (ERP) system with 15+ modules (including Inventory, HR, and Payroll) taught me more about React architecture than any tutorial ever could. Here is a breakdown of how we built a modern ERP system using Next.js and TypeScript, detailing directory patterns, query caching, component layouts, image optimizations, and SEO parameters.
Modular Folder Structure
Managing a multi-module enterprise project requires strict directory organization. We adopt a feature-driven App Router structure where each ERP module (e.g., Inventory, Payroll) is contained in its own folder with its specific layout, page components, and store slices, while sharing base UI components.
next-erp-app/
├── app/
│ ├── layout.tsx # Root layout with metadata and analytics
│ ├── page.tsx # Primary dashboard entrance
│ ├── inventory/ # Inventory Management Module
│ │ ├── layout.tsx # Module-specific sidebar and locks
│ │ └── page.tsx # Inventory tables and controls
│ ├── hr/ # Human Resources Module
│ │ ├── layout.tsx # HR layout
│ │ └── page.tsx # Attendance and employee lists
│ └── payroll/ # Payroll & Invoicing Module
│ ├── layout.tsx # Financial locks
│ └── page.tsx # Salary sheets and runs
├── components/
│ ├── ui/ # Shared atomic components (Button, Input, Dropdown)
│ └── common/ # Shared complex components (Sidebar, Topbar)
├── lib/ # Shared API helpers and utility libraries
│ ├── client.ts # Axios / Fetch client configuration
│ └── utils.ts # Helper utilities (date formats, currency calculations)
└── store/ # Global state management
├── useAuthStore.ts # Session and user permissions store
└── useNotifyStore.ts # System toast and banners storeState Management with Zustand Slices
Rather than using a single monolithic Redux store that triggers global re-renders, we use Zustand. Each ERP module maintains its own lightweight, independent store slice. This isolates state updates, improves rendering speed on complex tables, and keeps the code clean.
API Connection with TanStack Query
An ERP relies on real-time data fetching, background synchronization, and cache management to prevent unnecessary database queries. We use @tanstack/react-query to manage the client server cache, query invalidations, and optimistic updates.
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import axios from 'axios';
interface InventoryItem {
id: string;
name: string;
stockCount: number;
price: number;
}
// Custom hook to fetch products
export function useInventoryItems() {
return useQuery<InventoryItem[]>({
queryKey: ['inventory', 'items'],
queryFn: async () => {
const { data } = await axios.get('/api/inventory/items');
return data;
},
staleTime: 1000 * 60 * 5, // Cache values for 5 minutes before refetching
});
}Interactive UI: Shadcn UI & Framer Motion
To build a premium, keyboard-accessible, and animated interface rapidly, we combine Shadcn UI (accessible headless primitives styled with Tailwind CSS) with Framer Motion. This combination handles modal dialogs, slide-overs, and popups cleanly with responsive easing configurations.
import { motion, AnimatePresence } from 'framer-motion';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
interface ERPModalProps {
isOpen: boolean;
onClose: () => void;
title: string;
children: React.ReactNode;
}
export function ERPModal({ isOpen, onClose, title, children }: ERPModalProps) {
return (
<AnimatePresence>
{isOpen && (
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent className="overflow-hidden">
<motion.div
initial={{ opacity: 0, y: 15 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -15 }}
transition={{ duration: 0.2, ease: 'easeOut' }}
>
<DialogHeader>
<DialogTitle>{title}</DialogTitle>
</DialogHeader>
<div className="mt-4">{children}</div>
</motion.div>
</DialogContent>
</Dialog>
)}
</AnimatePresence>
);
}Image Optimization & Core Web Vitals
ERP applications process large volumes of user data and media (employee headshots, receipts, document thumbnails). To keep Core Web Vitals high (maintaining 95+ performance score), we leverage Next.js's native <Image /> component. This automatically serves WebP/AVIF compressed files, enforces lazy loading, and prevents layout shifts (CLS) by requiring explicit aspect ratios.
import Image from 'next/image';
export function EmployeeAvatar({ src, name }) {
return (
<div className="relative h-12 w-12 overflow-hidden rounded-full border border-line">
<Image
src={src || '/photos/abhi.jpg'}
alt={`${name} - Profile Image`}
fill
sizes="48px"
className="object-cover"
loading="lazy"
/>
</div>
);
}Advanced SEO & Structured Metadata
Even internal dashboards require solid SEO setups for public entryways, landing sheets, and documentation guides. We implement dynamic page metadata configurations, structured Person and Organization JSON-LD schemas, and a sitemap generator that registers all modules and blogs dynamically.
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: 'Enterprise ERP - High Performance Dashboards',
description: 'A modular, high-fidelity ERP workspace designed by Abi Solutions.',
openGraph: {
title: 'Enterprise ERP Portfolio',
images: [{ url: '/photos/abhijith-1.jpg' }],
}
};Verification & Takeaways
To build enterprise software that is fast, maintainable, and search-engine friendly:
- Isolate features in directory groupings to keep the code modular.
- Manage state slice mutations cleanly using Zustand.
- Optimize heavy API dependencies using TanStack Query for cache invalidation.
- Design premium, accessible layouts combining Shadcn UI with Framer Motion.
- Enforce strict TypeScript types to prevent runtime errors.
- Optimize images using next/image with WebP formats and proper sizes.