Skip to content
Back to files
File 02Declassified (partially)

Performance rescue: Vue 2 → 3

Vue 2 → Vue 3, rewritten from scratch — and slow before it even shipped.

Client
Catering enterprise, Germany
Role
Senior frontend engineer, de facto tech lead
Timeline
[redacted]
Stack
Vue 3 · TypeScript · TanStack Query · SignalR · Biome · Feature-based architecture

At a glance

  • 0

    request waterfalls left

  • 1

    typed source of truth for all queries

  • RT

    targeted real-time invalidation

  • 100%

    PRs formatted by machines, reviewed by humans

Fixing performance problems in a large Vue 3 rewrite

Context

The team was rewriting a large Vue 2 application in Vue 3. The new version used a feature-based architecture, TanStack Query for data fetching and SignalR for real-time updates. The stack was modern, but the application was already showing serious performance problems before it reached production.

The problem

  1. Inefficient code. Some parts of the application contained unnecessary complexity, nested loops and watchEffect calls that re-triggered themselves through their reactive dependencies.
  2. TanStack Query used as an event bus. Application state was being coupled through queries and mutations that were triggered from places that should not have needed to know about each other.
  3. Request waterfalls. Reactive queries inside watchEffect caused one request to trigger another, creating chains of dependent network calls instead of allowing the data flow to be handled explicitly.
  4. Review noise. Formatting was not enforced consistently, so pull requests contained changes that made it harder to focus on logic and architecture during review.

What I did

  1. Moved formatting and linting into tooling. I introduced Biome and git hooks so formatting and basic consistency checks happened automatically before code reached review.
  2. Separated responsibilities. I clarified the boundaries between modules and views and removed unnecessary coupling between stores.
  3. Centralised data access. API clients, queries, mutations and query keys were brought into one typed data layer, giving the application a single place to define how data is fetched and identified.
  4. Built targeted invalidation. I introduced a SignalR-driven invalidation mechanism so that each real-time message invalidates only the queries affected by that change.
  5. Removed chained requests. I eliminated the request waterfalls created by reactive queries, alongside dozens of smaller performance and architecture fixes.

Result

  • 0 remaining request waterfalls in the analysed flows
  • 1 typed data layer for API clients, queries, mutations and query keys
  • Targeted real-time invalidation driven by SignalR events
  • Automated formatting and linting before code review

The result was a much clearer data flow, no remaining request waterfalls in the affected flows and a development setup that catches formatting and other basic consistency problems automatically.

Why the migration mattered

The application was already being rewritten in Vue 3, but a newer framework did not automatically produce a better application. The rewrite had carried some of the old architectural habits into the new codebase.

Addressing those problems during the migration meant the team could finish the move to Vue 3 with a clearer data layer, fewer hidden dependencies and fewer opportunities to reintroduce the same class of performance problems.

Stack

Vue 3, TypeScript, TanStack Query, SignalR, Biome, feature-based architecture.

This is the kind of work I offer through my frontend performance audit and Vue 2 to Vue 3 migration services.

Got a slow app?

I've seen worse. Let's talk.

Let's talk