Skip to main content

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

AreaRule
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.tsx is 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-componentsCreate 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.
  • 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>
}