Skip to main content

Why I abandoned default exports completely

If you’re a TypeScript developer, you’ve probably used default exports at some point. They seem convenient, right? But after a few years of working with both default and named exports, I’ve made a decision: I’m done with default exports. Permanently.

Here’s why.

Default exports are ambiguous

The biggest problem with default exports is that they lack clarity. When you see:

import something from './module';

What is something? Is it a function? A class? An object? A constant? You have no idea unless you open that file. Compare that to:

import { usefulFunction } from './module';

Now it’s clear what you’re importing.

Default exports hide the intent of your code. When someone else (or future you) looks at the import statement, they’ll have to guess what’s being imported unless they know the module by heart.

Refactoring headaches

Imagine this scenario: You have a file with a default export function.

export default function processOrder(order) {
  // do something
}

Later, you decide to add another utility function to the file:

function validateOrder(order) {
  // validation logic
}

export default function processOrder(order) {
  // do something
}

Oops. Now you need to rename the default export to a named one because you can’t have two default exports. So you do:

export { processOrder, validateOrder };

Now, you have to update every file that imports processOrder. And if you’re dealing with a large codebase, this can be a nightmare.

Had you used named exports from the start, this wouldn’t be a problem. Refactoring would be as simple as adding another named export.

Inconsistent imports

Default exports can lead to inconsistent import statements. For example:

import processOrder from './order-utils';
import { validateOrder } from './order-utils';

Why is one a default import and the other a named import from the same file? It’s confusing.

Using named exports makes everything consistent:

import { processOrder, validateOrder } from './order-utils';

Much cleaner.

Better IDE support

Most modern IDEs offer auto-completion for named exports. When you type:

import { ... } from './module';

the IDE will suggest all available exports from that module. With default exports, you don’t get this benefit. You have to remember the exact name of the default export, which is annoying.

Easier to identify unused code

With named exports, tools like ESLint can easily identify unused imports and exports. With default exports, it’s trickier. The tooling has to assume that the default export might be used in a dynamic way.

Named exports make it clear which functions, classes, or constants are actually in use.

Better collaboration

If you’re working in a team, consistency matters. Using named exports forces everyone to be explicit about what they’re importing and exporting. This leads to fewer misunderstandings and cleaner code reviews.

How to migrate from default to named exports

If you’ve been using default exports heavily and want to switch, here’s a simple migration strategy:

  1. Identify default exports: Look for all instances of export default in your codebase.
  2. Rename them to named exports: Change them to export const or export function as needed.
  3. Update import statements: Replace default imports with named imports across your project.
  4. Run tests: Make sure everything still works as expected.

Final thoughts

Default exports aren’t inherently evil, but they introduce more problems than they solve. In TypeScript, where clarity and type safety are key, named exports win every time.

So yeah, I’ve abandoned default exports completely. It’s one of those changes that’s made my TypeScript projects cleaner, more maintainable, and less frustrating to work with. And I’m not going back.