Import and Export Rules
This is our agreed-upon convention for structuring files and managing imports in our Next.js (App Router) project.
✅ TL;DR Rules
| Area | Rule |
|---|---|
| Barrel Exports | ✅ Allowed only in packages or for grouped types |
| Component Folders | ✅ No barrel exports — import via folder path |
| Feature Folders | ✅ No barrel exports |
| Shared Utilities | ✅ Barrel exports allowed if tightly scoped |
export * | ❌ Never allowed |
| Circular Imports | ❌ Strictly forbidden |
📁 Folder Structure Pattern
🧩 Components
- Each component lives in its own folder
index.tsxis the entry for the main component- Sub-components are imported directly
src/
components/
button/
index.tsx
icon.tsx
// ✅ usage
import { Button } from '@/components/button';
import { Icon } from '@/components/button/icon';
❌ Do not create shared component barrels
🧱 When to Use a components/ Folder Inside a Component
✅ Yes — if the component has 3+ sub-components or logic-heavy parts
Use a components/ folder to group internal parts and keep things clean.
card/
index.tsx
components/
header.tsx
body.tsx
footer.tsx
user-avatar.tsx
❌ No — if the sub-component is simple or only used once
Just co-locate the sub-component at the same level.
card/
index.tsx
skeleton.tsx
🔒 Rule of Thumb
| # of Sub-components | Create components/ dir? |
|---|---|
| 1 | ❌ No |
| 3 or more | ✅ Yes |
🧭 This Rule of Thumb Also Applies to Folders Like utils/, hooks/, etc
The same principle we use for component substructure applies when deciding whether to create folders like utils/, hooks/ inside components or features.
✅ Yes — Create a Folder If:
- You have 2 or more unrelated files (e.g., multiple hooks or multiple utility functions).
- You're starting to see naming conflicts or unclear purposes.
❌ No — If only 1 file or tightly related logic
- Just co-locate the file at the same level.
🧠 Feature Modules
- No barrels at the feature root level
- Keep logic and UI colocated
- Keep imports precise
src/
features/
domain/
auth/
hooks/
utils.ts
login-form.tsx
signup-form.tsx
// ✅ usage
import { LoginForm } from '@/features/domain/auth/login-form';
📦 Packages (Shared Internal Libraries)
Barrel exports allowed here to simplify public API.
packages/
utils/
add.ts
subtract.ts
index.ts
// index.ts
export { add } from './add';
export { subtract } from './subtract';
// ✅ usage
import { add, subtract } from '@acemate/utils';
📐 Types
Group and re-export types in a single index.ts per domain or context.
// types/index.ts
export type { Flashcard } from './flashcard';
export type { User } from './user';
// ✅ usage
import type { Flashcard } from '@acemate/types';
❌ Forbidden Patterns
export * from './X'- Cross-importing from inside a barrel file (causes circular imports)
- Nested barrel exports (e.g.,
features/index.ts,components/index.ts) - Mixing client/server modules in shared barrels
💡 Example Imports in Practice
// ✅ Components
import { Card } from '@/components/card';
import { Footer } from '@/components/footer';
// ✅ Features
import { LoginForm } from '@/features/domain/auth/login-form';
// ✅ Utils (internal package)
import { formatDate } from '@acemate/utils';
// ✅ Types
import type { Flashcard } from '@acemate/types';
📦 Zod Import Convention
Always import Zod using the namespace import style:
import * as z from "zod/v4";
✅ Why
- Ensures proper tree-shaking in Next.js.
- Using
import { z } from "zod/v4";prevents Next.js from tree-shaking unused parts of Zod, which increases the client bundle size unnecessarily.
- Using
- Keeps consistency across the codebase.
🚫 Default Export Restriction
Do not use export default in our codebase except where required by Next.js.
- Allowed cases: Next.js special files such as
page.tsx,layout.tsx,loading.tsx,error.tsx. - Everywhere else: Always use named exports.
✅ Why
- Makes refactoring easier (you can rename symbols globally).
- Improves tree-shaking and bundle size.
- Avoids “mystery imports” where it’s unclear what the default export is.
✅ Do
export function add(a: number, b: number) {
return a + b;
}
// or
export function Component() {
return <h1>I am a component</h1>
}
❌ Don’t
export default function add(a: number, b: number) { // ❌ avoid
return a + b;
}
// or
export default function Component() { // ❌ avoid
return <h1>I am a component</h1>
}