---
name: react-import-export
description: Imports, exports, path aliases, import ordering, barrel-file discipline, circular-import prevention in React + TypeScript code. Use when writing imports/exports or debugging circular dependencies.
level: core
applies-to: [spa, ssr, electron, hybrid]
owns:
  - named-export rule (rules-registry.md#imports.named-only)
  - no deep relative paths (rules-registry.md#imports.no-deep-relative)
  - alias consistency across tooling (rules-registry.md#imports.alias-consistency)
  - relative-within-slice rule (rules-registry.md#imports.relative-within-slice)
  - no self-barrel import (rules-registry.md#imports.no-self-barrel)
  - no grouping-folder barrels (rules-registry.md#imports.no-grouping-barrels)
  - import-order groups (rules-registry.md#imports.order-groups)
  - `import type` placement inside groups (rules-registry.md#imports.type-only)
depends-on: [react-runtime-decision]
see-also:
  - react-file-organization
  - react-naming
  - react-typescript-standards
  - react-prettier
  - react-eslint
triggers:
  - imports
  - barrels
  - path aliases
  - circular import
  - default vs named
  - import order
allowed-tools: [Bash, Read, AskUserQuestion, Grep, Glob]
version: 0.5.0
---

# Import / Export

> Core · Owns: named exports, aliases, import order, barrels, circular-import prevention.

## Named exports only

```ts
export const orderApi = { /* ... */ };   // ✅
export default orderApi;                 // ❌
```

Named exports keep refactor and search reliable and let bundlers tree-shake.

**Framework / tool exceptions:**

| Subject | Convention |
| --- | --- |
| Next.js `page.tsx`, `layout.tsx` | `export default Page` (framework contract) |
| Tool configs (`vite.config.ts`, `orval.config.ts`, `next.config.ts`) | `export default { ... }` |
| `React.lazy` adapter | wrap a named export in a default-shaped promise |
| Everything else | named only |

```ts
const CheckoutPanel = lazy(() =>
  import('~features/checkout').then(m => ({ default: m.CheckoutPanel })),
);
```

## Aliases — most specific available

Use project-configured aliases. Match the architecture profile.

### Feature-based (default)

| Alias | Resolves to |
| --- | --- |
| `@/` | `src/` (general) |
| `~components` | `src/components/` |
| `~features` | `src/features/` |
| `~api` | `src/api/` |
| `~utilities` | `src/utilities/` |
| `~types` | `src/types/` |

### FSD-style

| Alias | Resolves to |
| --- | --- |
| `@/app/*`, `@/pages/*`, `@/widgets/*`, `@/features/*`, `@/entities/*`, `@/shared/*` | matching `src/*` folder |

```ts
import { apiClient } from '@/lib/api-client';            // ✅
import { Loader } from '~components/Loader';              // ✅
import { checkoutApi } from '~features/checkout';         // ✅ feature root
import { foo } from '../../../lib/foo';                   // ❌ deep relative
import { internal } from '~features/checkout/internal';   // ❌ past public API
```

## Alias config consistency

Keep alias config in sync across **all** tools:
- `tsconfig.json` `paths`
- Bundler (`vite.config.ts`, `next.config.ts`)
- Test runner (`vitest.config.ts`, `jest.config.js` `moduleNameMapper`)
- `.storybook/main.ts`

Adding an alias in one place without the others breaks tests or Storybook silently. Prefer **`package.json#imports`** for new projects — single source of truth supported by TS 5.8+, Next 15+, Vitest, Jest 30, Storybook 9.

## Relative vs alias

- Relative (`./`, `../`) only **within the same slice/feature** (or same component folder).
- Alias for anything outside the current slice.
- Cross-slice access always goes through the slice's public API — never a deep path.

```ts
// inside features/checkout/components/checkout-panel.tsx
import { useCheckoutTotals } from '../hooks/use-checkout-totals';   // ✅ same slice
import { Loader } from '~components/loader';                          // ✅ outside → alias
import { catalogApi } from '~features/catalog';                       // ✅ other slice public API
import { catalogApi } from '~features/catalog/api/catalog-api';       // ❌ deep into another slice
```

Exception: a slice that doesn't yet have a public API — use the most stable deep path until `index.ts` is added.

## Import order

Groups separated by blank lines. The Prettier import-sort plugin enforces this — see `react-prettier`.

```ts
// 1. React
import React, { useState, useCallback } from 'react';

// 2. Framework (Next, Remix, …)
import NextLink from 'next/link';

// 3. Third-party
import { useSuspenseQuery } from '@tanstack/react-query';

// 4. Alias (most-stable layer first)
import { apiClient } from '@/lib/api-client';
import { capitalizeFirstLetter } from '~utilities/strings/capitalize-first-letter';
import { Loader } from '~components/loader';
import { checkoutApi } from '~features/checkout';

// 5. Relative (same slice)
import { CheckoutRow } from './checkout-row';
```

`import type` sits in the same group as its source module — see `react-typescript-standards`.

FSD projects: alias groups follow layer order `app → pages → widgets → features → entities → shared`, each its own block.

## Barrels & public APIs

A barrel `index.ts` is allowed **only as the public API of a unit**:
- a slice / feature → `<slice>/index.ts`
- a shared component → `<component>/index.ts`

```ts
// features/checkout/index.ts — the feature's public API
export { CheckoutPanel } from './components/checkout-panel';
export { useCheckoutQueries } from './hooks/use-checkout-queries';
export type { Order, OrderDraft } from './types';
```

**Rules**
- Component `index.ts` exports the component + its public types only — never private children, stories, snapshots, tests.
- Prefer `export *` for the public surface unless name collisions force re-naming.
- **No barrels in grouping folders** (`components/`, `hooks/`, `utilities/`, `cells/`). Cause circular deps + hurt tree-shaking.
- **Inside a feature, never import your own feature's `index.ts`.** Sibling files import relatively. Self-import creates a circular dependency the bundler often hides until production minification.
- `types/index.ts` containing type declarations (not re-exports) is fine.

## Preventing circular imports

- One barrel per slice, at its root; never import it from inside.
- No nested or intermediate barrels.
- Cross-feature access via the slice public API.
- If A and B import each other, extract the shared part **downward** into `utilities/`, `lib/`, `types/`, or a shared component.

## Core rules

**MUST**
- Named exports for components, hooks, utilities, stores, API clients.
- Most specific alias available.
- One barrel per slice/feature at its root.
- Alias config synced across all tooling.

**MUST NOT**
- Default exports outside framework/tool contracts.
- Deep relative paths (`../../../`).
- Deep-imports past a slice's `index.ts`.
- Re-export barrels inside grouping folders.
- Import from own feature's `index.ts` from inside.
- Sibling-to-sibling cross-import on the same layer.

## Self-review

- [ ] Every export is named (or has a documented framework/tool reason).
- [ ] No deep relative paths.
- [ ] No deep imports past another slice's `index.ts`.
- [ ] One `index.ts` per slice; no barrels in grouping folders.
- [ ] Alias config synced across tsconfig + bundler + test + storybook.
- [ ] Import groups separated by blank lines.

## See also

- `react-file-organization` — slice anatomy and where `index.ts` lives.
- `react-naming` — alias slugs, file casing.
- `react-typescript-standards` — `import type`, `verbatimModuleSyntax`.
- `react-prettier` — import-sort plugin config.
- `react-eslint` — `import/no-cycle`, `import/no-internal-modules`.

## Failure modes

- **Deep imports past `index.ts`.** Resolve at runtime; couple consumer to internals.
- **Re-export barrels in grouping folders.** Break tree-shaking + invite circular deps.
- **Self-importing own slice barrel.** Cycle the bundler hides until production.
- **Alias drift.** TS sees it; tests don't; CI fails silently.
- **Mixing relative + alias for the same module.** Reviewers see the same import twice.
