
Prefer named exports
· Last updated
Prefer named exports.
Default exports feel lighter when a file has one main thing. I used them for years. Then I renamed a component, the file name updated, and half the imports still said the old name. TypeScript did not care. The default binding can be called whatever you want.
That is the bug.
What is a named export?
A named export is a binding with a name. You import that name, in braces.
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
import { add, subtract } from "./math.js";
You can alias it. The alias is explicit.
import { add as plus } from "./math.js";
A default export has no name at the import site. Each file invents one.
export default function add(a, b) {
return a + b;
}
import add from "./math.js";
import sum from "./math.js";
import MathFn from "./math.js";
All three compile. None of them have to match the function you wrote.
Why does that matter?
Let’s say you export a component as default ProductDrawer. A year later it is a dialog. You rename the function and the file to ProductDialog. The language service updates the paths.
It does not update this:
import ProductDrawer from "./ProductDialog.js";
There is no error. Search for ProductDialog and you miss the call sites that still say ProductDrawer.
A named export fails the build until every import moves, or you write import { ProductDialog as ProductDrawer }. Either way the mismatch is visible.
This is also why autocomplete is better. import { | } from a module lists the exports. A default import autocompletes the path, then lets you type any identifier.
Refactoring is the same story. Rename a named export in TypeScript and the language service can update the imports. That is the language service, not ESLint. I have seen people credit the linter for this. It is not the linter.
Does this help tree-shaking?
Sometimes. Not the way the old explainers said.
Tree-shaking needs static import / export. webpack’s own guide 🔗 shows the case that actually breaks. A default-exported object bag cannot be shaken apart.
export default {
add: (a, b) => a + b,
subtract: (a, b) => a - b,
};
import math from "./math.js";
math.add(1, 2);
The bundler sees you using math. It cannot prove subtract is dead. Both functions stay.
Named exports give it a binding per function.
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
import { add } from "./math.js";
subtract can go.
A default-exported function is fine. If nothing imports the module, the whole module can drop. The lie is “named exports tree-shake, default exports don’t.” The truth is “don’t default-export a namespace object.”
ESM itself does not tree-shake at runtime. The bundler does.
When is a default export the right call?
When the host requires one.
Next.js pages and layouts 🔗 default-export the component for that route. File-based frameworks do this so they have one obvious export to render. Fight that and you get The default export is not a React Component.
Do it there. Name the function anyway.
export default function Page() {
return <h1>Home</h1>;
}
The function name still shows up in stacks and React’s component tree. The default is for the framework. Everything else in the file can be named.
A module that is truly one thing, consumed by humans, still wants a named export. export function cn() beats export default function that every caller names differently.
What should you not do?
Default-export an object of helpers. That is a CommonJS hangover. It kills tree-shaking and it is a worse import *.
Re-export defaults through a barrel without naming them. export { default as Button } from "./Button.js" is the least-bad form. export { default } from "./Button.js" spreads the rename problem.
Ban default exports with an ESLint rule, then spend a day fighting page.tsx. Turn the rule off for the files the framework owns.
I’d start with named exports in application code. Default-export only where the framework has a gun to your head.
Stay in touch
Don't miss out on new posts or project updates. Hit me up on X for updates, queries, or some good ol' tech talk.
Follow @zkMake