/** * ESLint rule to forbid Next.js imports in lib/ directory * * The lib/ directory should be framework-agnostic. * Next.js imports (redirect, cookies, headers, etc.) should only be in app/ directory. */ module.exports = { meta: { type: 'problem', docs: { description: 'Forbid Next.js imports in lib/ directory', category: 'Architecture', recommended: true, }, fixable: null, schema: [], messages: { noNextImports: 'Next.js imports are forbidden in lib/ directory. Found: {{import}} from "{{source}}"', noNextRedirect: 'redirect() must be used in app/ directory, not lib/ directory', noNextCookies: 'cookies() must be used in app/ directory, not lib/ directory', noNextHeaders: 'headers() must be used in app/ directory, not lib/ directory', }, }, create(context) { const filename = context.getFilename(); const isInLib = filename.includes('/lib/'); // Skip if not in lib directory if (!isInLib) return {}; return { // Track import statements ImportDeclaration(node) { const source = node.source.value; // Check for Next.js imports if (source === 'next/navigation' || source === 'next/headers' || source === 'next/cookies' || source === 'next/router' || source === 'next/link' || source === 'next/image' || source === 'next/script' || source === 'next/dynamic') { // Check for specific named imports node.specifiers.forEach(spec => { if (spec.type === 'ImportSpecifier') { const imported = spec.imported.name; if (imported === 'redirect') { context.report({ node: spec, messageId: 'noNextRedirect', }); } else if (imported === 'cookies') { context.report({ node: spec, messageId: 'noNextCookies', }); } else if (imported === 'headers') { context.report({ node: spec, messageId: 'noNextHeaders', }); } else { context.report({ node: spec, messageId: 'noNextImports', data: { import: imported, source }, }); } } else if (spec.type === 'ImportDefaultSpecifier') { context.report({ node: spec, messageId: 'noNextImports', data: { import: 'default', source }, }); } }); } }, // Also check for require() calls CallExpression(node) { if (node.callee.type === 'Identifier' && node.callee.name === 'require') { if (node.arguments.length > 0 && node.arguments[0].type === 'Literal') { const source = node.arguments[0].value; if (source === 'next/navigation' || source === 'next/headers' || source === 'next/cookies' || source === 'next/router') { context.report({ node, messageId: 'noNextImports', data: { import: 'require()', source }, }); } } } }, }; }, };