Import Organization
Consistent import organization makes code easier to read and understand.
Import order is sorted for you
Prettier sorts the imports of every file with @ianvs/prettier-plugin-sort-imports, the same way it sorts Tailwind classes. Nobody sorts imports by hand, and agents never reorder them themselves: pnpm format, or saving in the editor, does it. See Import sorting.
"use client";
import { readFileSync } from "node:fs";
import Link from "next/link";
import { useState } from "react";
import { Button } from "@/components/ui";
import { r, s } from "@/lib/constants";
import { formatDate } from "../utils/formatDate";
import type { SampleProps } from "./Sample.types";
import "./sample.css";
Node's own modules, then packages, the project's @/ aliases, parent folders (../), and the same folder (./). Side-effect imports, such as CSS, go after every other import: the sorter never moves them, because the order they load in can matter.
Importing components
components/ui/, components/layout/ and components/shared/ each have an index.ts, and code outside a category imports from it, so imports stay short:
import { Button, Card } from "@/components/ui";
-
No
index.tsdirectly incomponents/. The import shows whether a component is a Design System Component, part of the site frame, or shared. -
Inside a category, components import each other by file, never through the category's own
index.ts. See Inside a folder, import files directly. -
A category's
index.tsexports only components a Client Component can import. A Client Component that imports a category pulls in everything itsindex.tsexports, so one server-only component in it breaks the build for every Client Component that imports the category. -
A component that reaches server-only code, such as authentication, the database, secrets or
next/headers, starts withimport "server-only";. It is left out of its category'sindex.ts, and imported from its own folder:// components/layout/siteHeader/SiteHeader.tsximport "server-only";// app/layout.tsximport { SiteHeader } from "@/components/layout/siteHeader";import "server-only";makes a mistake fail the build with a clear error, instead of sending server code to the browser. -
Tests mock
server-only. Outside a Next.js build,import "server-only";always throws, so a Vitest test of the component, or of anything that imports it, fails before it runs. Mock it once in the Vitest setup file, and list that file insetupFilesinvitest.config.mjs:// vitest.setup.mjsimport { vi } from "vitest";// server-only throws outside a Next.js build.vi.mock("server-only", () => ({}));
Import Safety
These four rules are enforced, and they are not about tidiness. They stop a whole class of bug: code that runs before the thing it needs exists.
1. No circular imports
A file never imports a file that imports it back — directly, or through a chain of other files.
// ❌ Bad — userService imports orderService, which imports userService
// services/userService.ts
import { getOrdersForUser } from "./orderService";
// services/orderService.ts
import { getUserById } from "./userService";
When two files import each other, one of them has to load first, and it sees the other half-finished. The code may work today and break when an unrelated import changes the loading order.
The fix is to move the shared part into a third file that both import, so the dependency runs one way:
// ✅ Good — both services import from a third file; neither imports the other
// services/userService.ts
import { findUserRecord } from "./userRecords";
// services/orderService.ts
import { findUserRecord } from "./userRecords";
Enforced by ESLint import-x/no-cycle.
2. Inside a folder, import files directly
A folder's index.ts is for code outside the folder. Code inside the folder imports its neighbours by file, never through the folder's own index.ts. Importing through your own index is the most common way a circular import is created by accident.
// card/components/cardHeader/CardHeader.tsx
// ✅ Good — import the file directly
import type { CardProps } from "../../types";
// ❌ Bad — the card index imports CardHeader, so this is a circle
import { Card } from "../..";
| Importing from | Import through |
|---|---|
| Outside the folder | The folder's index.ts: import { Card } from "@/components/ui/card" |
| Inside the folder | The file itself: import type { CardProps } from "../../types" |
A parent component imports its sub-components. A sub-component never imports its parent. If a child needs something from the parent, the parent passes it as a prop.
3. Define before use
A function or variable is written above the code that uses it. Read top to bottom, a file never refers to something that has not been defined yet.
// ✅ Good
const formatReport = () => "report";
export const sendReport = () => formatReport();
// ❌ Bad — formatReport is used above where it is defined
export const sendReport = () => formatReport();
const formatReport = () => "report";
Enforced by ESLint no-use-before-define.
4. Files define things; they do not run things
A file declares its functions and exports them. It does not call them as it loads. The calling happens later, inside a component or another function.
// ✅ Good — nothing runs when this file is imported
export const connectToDatabase = () => createConnection(DATABASE_URL);
// ❌ Bad — importing this file opens a database connection
export const database = createConnection(DATABASE_URL);
Code that runs on import makes the order files load in matter. Code that only defines things does not.
Enforced by ESLint unicorn/no-top-level-side-effects.