Why Large Frontend Components Don’t Scale (and How Small Ones Save You)
Modern frontend frameworks like Vue and React make it easy to build fast.
They also make it easy to build something that works — right up until the moment it becomes unmaintainable.
If you’ve spent time in production apps, you’ve probably met this file:
- One component
- Hundreds (or thousands) of lines
- Fetching data
- Managing state
- Rendering UI
- Handling validation
- Responding to events
- Performing side effects
Everything technically works.
But touching it feels dangerous.
This isn’t a framework problem.
It’s a component design problem.
How Big Components Are Born
Large components rarely start large.
They usually begin as:
“This page is unique, so I’ll keep everything here.”
Then:
- A feature gets added
- A condition sneaks in
- A new API response shape appears
- A one-off UI variation is needed
Each change feels reasonable in isolation. Over time, the component becomes a junk drawer of responsibilities.
The danger isn’t size alone.
It’s mixed intent.
Why Large Components Are Hard to Maintain
1. Cognitive Load Explodes
When a component handles multiple concerns, your brain has to juggle all of them at once.
You can’t:
- Quickly understand what it does
- Predict the impact of a change
- Confidently refactor without fear
Reading becomes archaeology.
2. Changes Become Risky
In large components:
- Logic is tightly coupled
- State changes have unclear ripple effects
- Side effects hide in lifecycle hooks
A “small” UI tweak can unexpectedly break data flow, validation, or behavior elsewhere.
3. Reuse Becomes Copy-Paste
When logic is buried inside a large component:
- You can’t reuse it cleanly
- You end up copying chunks of code
- Fixes need to be applied in multiple places
That’s how bugs get cloned.
4. Testing Becomes Painful
Large components are difficult to test because:
- You must mock too much
- Setup becomes complex
- Tests focus on implementation details instead of behavior
Smaller components naturally lead to more meaningful tests.
A Real-World Vue Example
❌ The “Everything” Component
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import axios from 'axios'
const users = ref([])
const loading = ref(false)
const error = ref<string | null>(null)
const search = ref('')
onMounted(async () => {
loading.value = true
try {
const response = await axios.get('/api/users')
users.value = response.data
} catch (e) {
error.value = 'Failed to load users'
} finally {
loading.value = false
}
})
const filteredUsers = () => {
return users.value.filter(user =>
user.name.toLowerCase().includes(search.value.toLowerCase())
)
}
</script>
<template>
<div>
<input v-model="search" placeholder="Search users" />
<div v-if="loading">Loading...</div>
<div v-if="error">{{ error }}</div>
<ul>
<li v-for="user in filteredUsers()" :key="user.id">
{{ user.name }}
</li>
</ul>
</div>
</template>
Breaking It Down
✅ Step 1: Extract Data Logic into a Composable
// useUsers.ts
import { ref, onMounted } from 'vue'
import axios from 'axios'
export function useUsers() {
const users = ref([])
const loading = ref(false)
const error = ref<string | null>(null)
onMounted(async () => {
loading.value = true
try {
const response = await axios.get('/api/users')
users.value = response.data
} catch {
error.value = 'Failed to load users'
} finally {
loading.value = false
}
})
return { users, loading, error }
}
✅ Step 2: Create a Focused UI Component
<!-- UserList.vue -->
<script setup lang="ts">
defineProps<{
users: { id: number; name: string }[]
}>()
</script>
<template>
<ul>
<li v-for="user in users" :key="user.id">
{{ user.name }}
</li>
</ul>
</template>
✅ Step 3: Compose in the Parent
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useUsers } from './useUsers'
import UserList from './UserList.vue'
const search = ref('')
const { users, loading, error } = useUsers()
const filteredUsers = computed(() =>
users.value.filter(user =>
user.name.toLowerCase().includes(search.value.toLowerCase())
)
)
</script>
<template>
<div>
<input v-model="search" placeholder="Search users" />
<div v-if="loading">Loading...</div>
<div v-if="error">{{ error }}</div>
<UserList :users="filteredUsers" />
</div>
</template>
What “Small Components” Really Means
Breaking components down doesn’t mean:
- One component per file no matter what
- Abstraction for its own sake
- Premature optimization
It means clear responsibility.
A good component usually answers one question:
- “Display this data”
- “Handle this interaction”
- “Control this piece of state”
If a component is hard to name, it’s probably doing too much.
Frameworks Change. Principles Don’t.
Whether you’re using:
- Vue
- React
- Svelte
- Or whatever comes next
The rule stays the same:
Clean boundaries age well. Monolith components don’t.
Small components aren’t about elegance.
They’re about longevity.
Your future self will thank you.
Final Thought
If you’re working in a codebase where:
- Files feel fragile
- Changes feel scary
- Refactors never happen
Don’t start with a rewrite.
Start by shrinking the biggest component in the room.
That’s usually where clarity begins.
Member discussion