How Default Exports Simplifies Module Structure
Default exports work best for modules with a single primary export - classes, main components, or primary functions. This pattern creates clean imports without curly braces and clearly communicates that a module has one main purpose. Teams using default exports report simpler import statements and better module clarity.
TL;DR
- Export single main item with
export default ClassName
- Import without braces:
import Component from './Component'
- Perfect for classes, React components, and main functions
- Enables renaming imports for better context
const defaultExport = class Database { connect() { return this } }
The Single Responsibility Challenge
You're creating a module that serves one primary purpose - a database connection class, a React component, or a configuration object. While the module might have helper functions, there's clearly one main export that consumers care about. You need a clean way to indicate this primary export.
// database.js - Unclear main export
function connect(url) {
console.log('Connecting to:', url)
return { connected: true }
}
function disconnect() {
console.log('Disconnecting')
return { connected: false }
}
function query(sql) {
console.log('Executing:', sql)
return []
}
// Which is the main export?
const database = { connect, disconnect, query }
console.log('Database object:', database)
Default exports make the primary export obvious and enable cleaner import syntax:
// database.js - Clear default export (simulated)
class Database {
constructor(url) {
this.url = url
this.connected = false
console.log('Database instance created for:', url)
}
async connect() {
console.log('Connecting to:', this.url)
this.connected = true
return this
}
}
const defaultExport = Database // Export as default
console.log('Database class exported')
Best Practises
Use default exports when:
- ✅ Module has one clear primary export (class, component)
- ✅ Creating React components or Vue components
- ✅ Exporting configuration objects or main functions
- ✅ Want to allow import renaming for better context
Avoid when:
- 🚩 Module exports multiple equally important functions
- 🚩 Creating utility libraries with many functions
- 🚩 Exporting constants that are better as named exports
- 🚩 Need tree-shaking of individual functions
System Design Trade-offs
Aspect | Default Export | Named Export |
---|---|---|
Import Syntax | Clean - no curly braces | Explicit - requires braces |
Renaming | Easy - rename during import | Complex - requires 'as' keyword |
Bundle Analysis | Clear - single main export | Detailed - shows specific imports |
Refactoring | Flexible - rename without breaking | Rigid - exact name matching |
Intent | Clear - this is THE export | Neutral - one of many exports |
Tree Shaking | All or nothing approach | Fine-grained elimination |
More Code Examples
❌ Multi-export confusion
// UserProfile.js - Unclear which export is main
function UserProfile(props) {
console.log('Rendering profile for:', props.user.name)
return {
render: () => `<div>Profile: ${props.user.name}</div>`,
}
}
function UserAvatar(props) {
console.log('Rendering avatar for:', props.user.name)
return {
render: () => `<img src="${props.user.avatar}" />`,
}
}
function UserActions(props) {
console.log('Rendering actions for user:', props.user.id)
return {
render: () => '<div>Edit | Delete</div>',
}
}
const USER_ROLES = {
ADMIN: 'admin',
USER: 'user',
GUEST: 'guest',
}
// Consumer confused about main component
// import { UserProfile, UserAvatar, UserActions } from './UserProfile';
// Which one is the primary component?
const testUser = {
id: 1,
name: 'Alice',
avatar: '/avatars/alice.jpg',
role: 'user',
}
const profile = UserProfile({ user: testUser })
console.log('Rendered profile:', profile.render())
console.log('Multiple exports without clear hierarchy')
✅ Clear default priority
// UserProfile.js - Clear default with supporting exports (simulated)
const USER_ROLES = { ADMIN: 'admin', USER: 'user', GUEST: 'guest' }
function UserAvatar({ user }) {
console.log('Rendering avatar for:', user.name)
return {
render: () => `<img src="${user.avatar}" alt="${user.name}" />`,
}
}
function UserActions({ user }) {
console.log('Rendering actions for user:', user.id)
const canEdit = user.role !== USER_ROLES.GUEST
return {
render: () => (canEdit ? '<div>Edit | Delete</div>' : '<div>View Only</div>'),
}
}
function UserProfile(props) {
console.log('Rendering main profile component for:', props.user.name)
const avatar = UserAvatar({ user: props.user })
const actions = UserActions({ user: props.user })
return {
render: () => `<div class="user-profile">
<h2>${props.user.name}</h2>
${avatar.render()}
<p>Role: ${props.user.role}</p>
${actions.render()}
</div>`,
}
}
// Simulating exports
const defaultExport = UserProfile // Default export
const namedExports = { UserAvatar, UserActions, USER_ROLES }
// Consumer gets clean import syntax
const testUser = {
id: 1,
name: 'Alice',
avatar: '/avatars/alice.jpg',
role: USER_ROLES.USER,
}
const profileComponent = UserProfile({ user: testUser })
console.log('Main component rendered')
console.log('Default export makes primary component obvious')
Technical Trivia
The React Import Disaster of 2020: A large team was building a design system with hundreds of components. They used named exports for everything, leading to massive import statements like import { Button, Card, Modal, Input, Select, Dropdown, ... } from '@company/ui'
with 30+ imports per file.
Why named-only exports failed: Developer productivity plummeted as import statements became unmanageable. Bundle analysis was impossible since every component was a named export. The main components weren't obvious among dozens of utilities and sub-components.
Default exports saved the day: Switching primary components to default exports created clean imports: import Button from '@company/ui/Button'
. Bundle analysis became clear, main components were obvious, and import statements stayed readable even in complex applications.
Master Default Exports: Single Purpose Modules
Use default exports when your module has one clear primary purpose - a class, React component, or main function. This pattern communicates intent and simplifies imports. Combine with named exports for supporting utilities when needed. Avoid default exports for utility libraries where all functions are equally important.