Open almost any frontend project’s Jira board.
You’ll find:
- New features
- Bugs
- Improvements
- Customer requests
- Maybe a few technical tasks
What you probably won’t find is a ticket saying:
“This component architecture is becoming painful to work with.”
Or:
“We have three different ways of fetching data.”
Or:
“Nobody knows which of these utilities should be used.”
Or:
“Changing this shared component now requires checking 17 unrelated screens.”
These are real engineering problems.
They just don’t look like Jira tickets.
And that’s one of the reasons frontend technical debt keeps growing.
Technical debt isn’t only unfinished work
When developers hear “technical debt”, they often think about old code that needs to be rewritten.
That’s only part of it.
Technical debt is any existing technical decision or implementation that makes future development more expensive, slower, or riskier than it needs to be.
Sometimes it’s obvious.
TODO: rewrite this mess
Sometimes it isn’t.
A component has 47 props.
Nobody wants to touch it.
So the next developer adds prop number 48.
The code still works.
The Jira ticket is closed.
The debt just increased.
That’s frontend technical debt.
The frontend is especially good at hiding debt
Backend systems usually have relatively clear boundaries.
Services.
Databases.
APIs.
Queues.
Infrastructure.
Frontend applications are often much more fluid.
A component can contain:
- UI
- business logic
- API calls
- state management
- validation
- permissions
- analytics
- formatting
- side effects
And because everything still renders correctly, it’s easy to convince ourselves that the architecture is fine.
Until we need to change something.
Then a “small” feature becomes a three-day task.
Not because the feature is difficult.
Because the codebase has accumulated friction.
The problems nobody puts in Jira
Here are some of the most common examples I’ve seen.
1. Component duplication
You need a confirmation dialog.
There is already one.
But nobody knows where it is.
So someone creates another one.
Six months later:
BaseModal.vue
ConfirmModal.vue
ConfirmationDialog.vue
DeleteDialog.vue
ConfirmDialog.vue
They all do roughly the same thing.
None of them are technically broken.
But now every developer has to answer another question:
“Which one should I use?”
That’s debt.
The cost isn’t the duplicated code.
The cost is the additional decision every developer has to make.
2. Multiple ways of doing the same thing
This is one of my favourite forms of frontend debt.
Imagine a project with three different API patterns:
await $fetch("/api/users");
Then somewhere else:
await apiClient.get("/users");
And somewhere else:
const { data } = await useUsers();
All three work.
The problem is that the project no longer has one obvious way to communicate with the backend.
The same happens with:
- forms
- validation
- modals
- notifications
- permissions
- routing
- state management
- date formatting
- error handling
Once there are multiple patterns, developers start spending cognitive capacity deciding how to implement something instead of what to implement.
That cost rarely appears in Jira.
3. Components that became everything
It starts innocently.
UserForm.vue
It handles a form.
Then someone adds validation.
Then permissions.
Then API calls.
Then loading states.
Then error handling.
Then a special case for administrators.
Then another special case for editing.
Eventually:
UserForm.vue
is 1,200 lines long.
The component still works.
Nobody creates a Jira ticket because there is no visible bug.
But every change becomes dangerous.
That’s technical debt.
4. CSS and design-system drift
This one is particularly easy to ignore.
The design system says:
spacing-4
But the application contains:
margin: 13px;
margin: 15px;
margin: 17px;
margin: 19px;
The design system has a button component.
Developers keep creating custom buttons.
There is a standard card component.
Half the application uses hand-written cards.
Again, nothing is necessarily broken.
The problem appears later.
A design change that should take one place now requires finding dozens of variations.
The debt is not the CSS itself.
The debt is divergence.
5. “Temporary” abstractions
This is one of the most expensive words in software development.
“Let’s just create a small abstraction for now.”
Temporary code has a habit of becoming permanent infrastructure.
A helper is introduced for one feature.
Then another feature uses it.
Then it becomes a shared utility.
Then nobody remembers its original constraints.
Five developers later, changing it becomes almost impossible because half the application depends on behaviour that was never intended to be a public contract.
The Jira ticket that created the abstraction is long gone.
The abstraction remains.
6. Dependency debt
You don’t necessarily need an outdated dependency to have dependency debt.
You can have 37 libraries solving problems your team barely understands.
Or three libraries doing almost the same thing.
Or a package that was introduced years ago and is now used in one obscure component.
The real problem is often this:
Nobody knows why the dependency exists anymore.
That makes removing it risky.
So nobody removes it.
The dependency stays.
And every dependency adds maintenance, security, upgrade, and cognitive cost.
7. Testing debt
A test suite can have technical debt too.
For example:
1,200 tests
sounds impressive.
Until you discover that most of them test implementation details.
Or that nobody trusts the tests.
Or that changing a component requires updating 30 snapshots.
Or that tests take 15 minutes to run locally.
Eventually developers stop running them.
Now you have the worst possible combination:
A large test suite that doesn’t provide confidence.
Again, the application still works.
The Jira board remains clean.
The debt grows.
8. Accessibility debt
Accessibility is another area where frontend debt can remain invisible for a long time.
A button works.
A modal opens.
A form submits.
Everything looks fine.
Until someone tries to use the application without a mouse.
Then you discover:
- missing keyboard navigation
- incorrect focus management
- missing labels
- poor heading structure
- insufficient contrast
- inaccessible custom components
Accessibility debt is particularly dangerous because fixing it later can require changing shared components and patterns across the entire application.
It’s much cheaper when accessibility is part of the system from the beginning.
9. Performance debt
Performance debt follows the same pattern.
One unnecessary API request isn’t necessarily a problem.
One large component isn’t necessarily a problem.
One expensive computed value isn’t necessarily a problem.
But they accumulate.
Then one day someone opens the application on a slower device and asks:
“Why is this page taking three seconds to become interactive?”
The answer is rarely one bad line of code.
It’s usually hundreds of small decisions.
That’s what makes technical debt difficult to see.
The most dangerous debt is the debt that doesn’t hurt yet
This is the important part.
Technical debt doesn’t necessarily produce an error.
It produces friction.
A developer needs two hours instead of thirty minutes.
A feature requires touching six files instead of two.
A code review takes longer because nobody knows which pattern is correct.
A new developer asks the same architectural question for the third time.
A small design change requires checking twenty components.
Nothing is “broken”.
But development is getting slower.
That’s the interest you’re paying on the debt.
Jira isn’t necessarily the solution
You might think the obvious answer is:
“Put all of it in Jira.”
I’m not convinced.
Not everything needs a Jira ticket.
If every small architectural annoyance becomes a ticket, your backlog becomes a graveyard of things nobody will ever prioritize.
You’ll end up with:
FE-1234 Refactor UserForm
FE-1288 Consolidate buttons
FE-1302 Clean up API clients
FE-1341 Fix inconsistent spacing
FE-1377 Improve tests
FE-1412 Remove old dependency
And six months later:
Status: To Do
The problem isn’t that the team didn’t document the debt.
The problem is that they created documentation without creating visibility into the cost of the debt.
Start tracking friction instead
I prefer to think about frontend technical debt as a debt ledger rather than just a backlog.
It doesn’t need to be complicated.
For example:
# Frontend Debt
## Multiple API access patterns
### Problem
The application currently uses three different approaches
for communicating with the backend.
### Impact
- New developers don't know which approach to use
- Error handling is inconsistent
- Authentication behaviour differs between clients
- Refactoring becomes harder
### Cost
Every new feature requires deciding which API pattern to follow.
### Direction
Standardise on the shared API client.
### Status
Accepted debt
Now the problem has a name.
More importantly, it has context.
This is very similar to the reasoning behind Architecture Decision Records. ADRs preserve why an architectural decision was made; a debt ledger can preserve why an existing piece of friction matters and what direction the team wants to take.
I’ve written more about preserving architectural reasoning in Why Every Frontend Team Needs Architecture Decision Records (ADRs).
Not all technical debt should be paid immediately
This is another mistake teams make.
Technical debt isn’t automatically bad.
Sometimes you deliberately take it.
You have two weeks to ship.
You know the abstraction isn’t perfect.
You ship the feature.
That’s fine.
The problem is pretending that the debt doesn’t exist.
There is a huge difference between:
“This is bad code.”
and:
“We knowingly accepted this trade-off because shipping now is more valuable.”
The second is an engineering decision.
The important part is making the decision visible.
A simple way to identify frontend debt
When working on a feature, pay attention to the things that slow you down.
Not just bugs.
Friction.
Ask:
Why did this take longer than it should have?
Maybe you had to search for the correct component.
Maybe three different abstractions existed.
Maybe you had to understand 800 lines of component code before making a five-line change.
Maybe the tests were impossible to understand.
Maybe the design system wasn’t followed.
Maybe the API layer made a simple request unnecessarily complicated.
Those moments are signals.
Write them down.
You don’t necessarily need a Jira ticket.
But don’t let the observation disappear.
I like four categories
When I encounter frontend debt, I usually classify it into four buckets:
Architecture
Examples:
- duplicated API clients
- unclear state ownership
- inconsistent data-fetching patterns
- excessive coupling
- unclear component boundaries
Developer Experience
Examples:
- slow builds
- confusing project structure
- difficult local setup
- inconsistent tooling
- poor debugging experience
Product Quality
Examples:
- accessibility gaps
- performance problems
- inconsistent UI
- missing error states
- unreliable responsive behaviour
Maintainability
Examples:
- duplicated components
- large components
- outdated dependencies
- inconsistent conventions
- fragile tests
This makes the discussion much easier.
Instead of saying:
“The frontend is getting messy.”
You can say:
“We have duplicated API abstractions that are increasing development cost.”
That’s an engineering problem.
And engineering problems can be discussed.
AI makes this even more important
There is another reason I think frontend debt deserves more attention now.
AI makes it incredibly easy to add more code.
That’s both its strength and its danger.
If your codebase has three API patterns, AI can happily use the fourth.
If you have five similar components, AI can create the sixth.
If your architecture is inconsistent, AI doesn’t automatically fix it.
It can scale the inconsistency.
This is why architectural context matters more in AI-assisted development.
The same principle applies to ADRs: explicit project knowledge gives AI something concrete to work from rather than forcing it to infer everything from the codebase.
ADRs can explain why.
Specs can explain what should change.
The code shows what exists.
A debt ledger explains what is currently hurting.
Together, these give both humans and AI a much better picture of the project.
The real cost of technical debt
The cost of technical debt isn’t the time it takes to rewrite something.
It’s the additional cost paid by every future change.
Imagine a feature that normally takes one day.
Because of accumulated debt, it takes two.
That’s one day of interest.
Do that twenty times and you’ve lost twenty development days without ever having a Jira ticket called:
Pay frontend technical debt
That’s why technical debt can be so difficult to explain to stakeholders.
Nothing is obviously broken.
The team is just slower.
Final thoughts
I don’t think frontend teams need another giant process for managing technical debt.
They need to become better at noticing it.
The next time you finish a feature and think:
“That was much harder than it should have been.”
Stop for a moment.
Ask why.
Maybe the answer is the feature.
Maybe the answer is the architecture.
Maybe it’s the design system.
Maybe it’s the tests.
Maybe it’s three different abstractions doing the same thing.
That’s your debt signal.
Don’t necessarily create another Jira ticket.
Create visibility.
Give the problem a name.
Document the impact.
And when the cost becomes high enough, pay the debt.
Because the most dangerous technical debt isn’t the code you know is bad.
It’s the friction you’ve become so used to that you no longer notice it.
Your Jira board tells you what the team is building.
Your technical debt tells you how expensive it is becoming to build it.