Files
gridpilot.gg/apps/website/eslint-rules/no-nextjs-imports-in-ui.js
2026-01-13 12:10:15 +01:00

95 lines
2.8 KiB
JavaScript

/**
* 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 },
});
}
}
}
},
};
},
};