Every module bundler and JavaScript environment supports both default and named exports. Most style guides pick one and stick with it. Here is why named exports are the right choice for most code.

What default exports allow

// math.js
export default function add(a, b) { return a + b; }

// consumer1.js
import add from "./math";

// consumer2.js
import sum from "./math"; // completely different name, same export

// consumer3.js
import please_work from "./math"; // still valid

Default exports let every consumer choose their own name. The module does not enforce anything. add, sum, and please_work all import the exact same function.

Why this breaks refactoring

When you rename add to addition inside math.js, nothing in the consumers breaks because they never imported add by name. But the consumers are still calling it add or sum, and any developer reading the code has to mentally map the local name back to the source file to understand what the function is.

More critically, “find all usages of add” no longer works reliably. If you search for add in your codebase, you will miss sum and please_work. If you search for the file, you find the consumers but not what they call it.

Named exports prevent this:

// math.js
export function add(a, b) { return a + b; }

// consumer.js
import { add } from "./math";
// Cannot be renamed without a deliberate decision

Now “find all usages of add” works across the entire codebase. Renaming tools in editors work correctly. The name is consistent everywhere.

Tree shaking is simpler with named exports

Bundlers like webpack and Rollup can tree-shake both default and named exports, but named exports make it more straightforward. When a module has named exports, the bundler can clearly see which exports are used. With a default export that is an object, the analysis is harder:

// Harder to tree-shake
export default { add, subtract, multiply, divide };

// Easy to tree-shake
export { add, subtract, multiply, divide };

In the second case, if only add is imported, the bundler can confidently exclude the rest.

Re-exporting is cleaner

Barrel files (index.js that re-exports from many modules) are common in large codebases. Re-exporting named exports is explicit:

// utils/index.js
export { add, subtract } from "./math";
export { format, parse } from "./dates";
export { capitalize, truncate } from "./strings";

Re-exporting default exports requires making up a name:

export { default as add } from "./math";
export { default as format } from "./dates";
// The "default as name" pattern is awkward and easy to get wrong

Autocomplete and discoverability

Named exports are self-documenting. When you type import { } from "./math", your editor can show you every available export from that module. With a default export, you have to know what the module exports, look at the file, or trust documentation.

This matters especially when onboarding to a codebase. Named exports let you see the API surface of a module from any consumer.

The case for default exports

Default exports make sense in some specific situations:

React components: Many teams use default exports for React components because the file name and component name are the same concept. import Button from "./Button" reads naturally. However, even here, named exports work fine: import { Button } from "./Button".

Entry points: When a module is a single-function utility that does one thing, a default export is not harmful. A module that exports only add and nothing else does not benefit much from named export convention.

Dynamic imports: import("./module").then(m => m.default) works but is slightly more awkward than import("./module").then(({ specificExport }) => ...).

A practical rule

Prefer named exports everywhere. Use default exports only for React components as a team convention or when a module genuinely exports exactly one thing and will never export more.

If you are working in a codebase that uses both inconsistently, try to be consistent within a file and a feature area. The most expensive part of default exports is not the syntax, it is the loss of searchability and the maintenance burden of multiple names for the same thing.

// Preferred
export function createUser(data) { ... }
export function deleteUser(id) { ... }
export function updateUser(id, data) { ... }

// Avoid
export default {
  create: createUser,
  delete: deleteUser,
  update: updateUser,
};

Named exports treat the module as a namespace. Every export has a canonical name. Consumers use that name or use as to rename explicitly, which is visible and searchable. That is the right default.