/** * ESLint rule to forbid Next.js imports in components/ and ui/ * * Next.js imports (from 'next/*') should only be used in: * - app/ directory (pages, layouts, routes) * - Server Actions * * Components and UI elements should be framework-agnostic. * Navigation and routing logic should be passed down from pages. * * Rationale: * - Keeps components/ui portable and testable * - Prevents framework coupling in reusable code * - Forces clear separation of concerns */ module.exports = { meta: { type: 'problem', docs: { description: 'Forbid Next.js imports in components/ and ui/ directories', category: 'Architecture', recommended: true, }, fixable: null, schema: [], messages: { noNextImports: 'Next.js imports are forbidden in {{dir}}/. Pass navigation/routing from app/ or use callbacks.', }, }, create(context) { const filename = context.getFilename(); const isInComponents = filename.includes('/components/'); const isInUi = filename.includes('/ui/'); if (!isInComponents && !isInUi) return {}; const dir = isInComponents ? 'components' : 'ui'; // Next.js modules that should not be imported const forbiddenModules = [ 'next/navigation', // useRouter, usePathname, useSearchParams 'next/router', // useRouter (pages router) 'next/link', // Link component 'next/image', // Image component 'next/script', // Script component 'next/head', // Head component 'next/dynamic', // dynamic import 'next/headers', // cookies, headers (server-only) 'next/cache', // revalidatePath, revalidateTag ]; return { ImportDeclaration(node) { const importSource = node.source.value; // Check if importing from forbidden modules if (forbiddenModules.includes(importSource)) { context.report({ node, messageId: 'noNextImports', data: { dir }, }); } // Also check for direct next/* imports if (importSource.startsWith('next/')) { context.report({ node, messageId: 'noNextImports', data: { dir }, }); } }, // Also check for dynamic imports CallExpression(node) { if (node.callee.type === 'Identifier' && node.callee.name === 'import') { if (node.arguments.length > 0 && node.arguments[0].type === 'Literal') { const importPath = node.arguments[0].value; if (typeof importPath === 'string' && importPath.startsWith('next/')) { context.report({ node, messageId: 'noNextImports', data: { dir }, }); } } } }, }; }, };