Long Term Performance Starts with Better Systems

Savita VermaSavita Verma
9 min read

Our code might be fast today, but performance is not just about code. It is about what happens around the code. It is about the tools you build, the systems you automate, and the habits we form.

In Part 1 of this series, we learned about why performance matters in the first place. In Part 2, we looked at how to optimize every layer of your stack, from backend queries to frontend bundles. Now in Part 3, we will shift our focus. We are going to learn about what keeps your app fast after code is merged.

Let us begin with something simple. Making performance invisible by offloading work.


Async Workflows That Do Not Block the User

Not all work has to happen during the request response cycle. Sometimes, performance improvements come from stepping out of the way.

Suppose our app lets users submit some data, like uploading a file or submitting a form. After the submission, we need to do a few things like:

  • Generate a report

  • Send an email to someone

  • Save logs for analytics

We could do all this work during the API request. But then our users will have to wait. And that wait can be long.

A better approach is to use a background job system. This means our API only accepts the request and queues up the rest of the work. The actual processing happens separately, outside of the main response cycle.

Here is example using Bull and Redis:

// Inside your API route
queue.add('generateReport', { submissionId });

// In your background worker
queue.process('generateReport', async (job) => {
  const data = await Submission.findById(job.data.submissionId);
  await generatePDF(data);
  await notifyInstructor(data);
  await logToAnalytics(data);
});

This way, the user gets an instant response. They are not blocked by all the backend work. If the report fails to generate, it can be retried automatically. And if the queue grows, we can scale the workers independently. This small pattern creates a big improvement in perceived speed.


Using Serverless for Low Frequency Jobs

Sometimes we have tasks that do not run often. These might include things like:

  • Sending a weekly email digest

  • Deleting old records every night

  • Checking inactive accounts once a week

We don’t need a full backend server running all the time just to handle these jobs. Instead, we can use serverless functions.

Serverless functions run only when triggered. We can schedule them to run at fixed intervals using services like AWS Lambda, Firebase background functions, or Vercel’s cron jobs.

For example, if we want to send a weekly report every Monday at 8 AM, we can write a function that gathers the data and sends the email and set a schedule, deploy it once, and let it run on its own.

This approach reduces our maintenance and hosting costs. And we don’t need to worry about uptime or servers running 24 by 7.

More importantly, it helps you keep performance clean. We are not adding more load to your main app just to run jobs that happen once a week.


Defer Non-Critical Work on the Frontend

Performance is not just about backend work. Even the frontend can slow down when it tries to do too much at once.

Suppose we want to save the user’s scroll position so that they can return to the same spot later. This is useful, but it is not urgent. If we try to save the scroll position during every scroll event, it might cause jank.

A better way is to use requestIdleCallback, which tells the browser to run your code when it is not busy.

requestIdleCallback(() => {
  localStorage.setItem('scrollPosition', window.scrollY);
});

This way, we avoid blocking the user experience and our let the browser breathe and save the data when there is free time.

If requestIdleCallback is not supported, we can also use a delayed setTimeout. The idea is the same. Do the work, but do it later.

setTimeout(() => {
  localStorage.setItem('scrollPosition', window.scrollY);
}, 500);

Not every action needs to feel instant. Some things just need to feel invisible.


Automation That Watches Your Performance

We cannot fix what we don’t measure. But if we try to measure everything manually, it quickly becomes overwhelming.

The goal is not to watch every metric by hand. The goal is to build just enough automation so that machines can catch the problems before users do.

Use Lighthouse in Your CI Pipeline

Lighthouse is a tool that checks our app’s performance, accessibility, and more. It gives us a score and shows areas of improvement.

We can run Lighthouse in your CI pipeline on every pull request. This lets our catch performance drops during code review.

Here is an example, we can set budget in your config:

"performance": {
  "maxAssetSize": 250000
}

If someone adds a huge dependency or slows down our core route, the build will fail and stop regressions before they reach production.

GitHub Actions, GitLab CI, and Vercel all support Lighthouse integrations. It is easy to add and provides real feedback on every deploy.

Set Up Monitoring Dashboards

Once app is live, we no longer the only one using it. People from different places, devices, and networks are interacting with it in ways we can’t predict. So how do we know if it’s still performing well?

This is where monitoring dashboards help.

We can use tools like New Relic, Sentry, or Grafana with Prometheus to collect live performance data like:

  • How long our APIs take to respond

  • Whether JavaScript errors are increasing

  • If page load times are slower in some countries

For example, we might see that the /login route is loading in 800ms on your machine, but real users in Southeast Asia are waiting 3 seconds. That insight does not come from local testing, it comes from real monitoring.

When we have dashboards like these, we are not guessing anymore. We can see problems as they appear and respond with confidence, not panic.

Avoid Alert Fatigue

Getting notified about issues is important. But getting too many alerts especially for things that don’t really matter becomes counter productive.

Imagine getting a Slack ping every time one user sees a 400ms API response. At first, you’ll pay attention. After a week of nonstop alerts? You’ll probably mute the channel.

Instead of alerting on every small dip, use smarter rules like:

  • Only alert when 95 percent of page loads are slower than 2.5 seconds, consistently across three releases

  • Or trigger a warning if 15 percent of all users are hitting slow APIs within a short time window

This way, alerts only fire when something is really wrong and affecting many people not just a temporary spike or one slow user.

This way you stay informed, but not overwhelmed.


Regression Testing With Real Data

Once our app is running smoothly, the next challenge is keeping it that way. Code changes, new features, and even small updates can accidentally slow things down. This is where performance regression testing comes in.

There are two main ways to do this:

  1. Synthetic testing runs in a controlled environment

  2. Real user monitoring collects real-world data from actual users

Synthetic Testing

Synthetic testing is like running a lab experiment. We use tools to simulate how your app performs under certain conditions.

Some useful tools for this are:

  • WebPageTest

  • SpeedCurve

  • Calibre

With these, we can test important routes like your homepage, dashboard, or checkout screen. We can simulate different devices (like a mid-range Android phone), and slow networks (like 3G), to see how our app performs under stress. This helps us catch regressions early.

For example, if a recent UI change increased the page load time by 1 second, we will see that right away in our synthetic test reports.

But here’s the catch this only tells us what could happen, not what is actually happening for real users.

Real User Monitoring

Real user monitoring (often called RUM) works very differently. It doesn’t simulate anything. Instead, it collects data from people who are actually using our app on real devices, over real internet connections, in all kinds of environments.

Let’s say our synthetic test shows that our course page loads in 1.2 seconds. That sounds fast, right?

But when we check the RUM data, we will notice that users in rural areas are seeing load times closer to 3.8 seconds. That’s a big gap.

Why does this happen? Maybe their connection is slow. Maybe fonts are loading from a faraway CDN. Or maybe that high-resolution background image is too large for their network.

Whatever the reason, RUM shows the truth how fast (or slow) things feel to real people.


Make Performance a Daily Habit

Performance should not be treated like a special project or something we only think about before a big release. It works best when it becomes part of your regular development flow just like writing tests or reviewing code.

Add Performance Checks to Every Pull Request

One of the easiest ways to stay consistent is to include a short performance checklist in your pull request descriptions.

Before merging, ask yourself:

  • Are new images lazy-loaded?

  • Did I check how this affects bundle size?

  • Are database queries paginated or scoped?

These are simple questions, but they catch problems early. If you do this for every feature or fix, performance becomes something you check by habit, not just during a crisis.

We can add this checklist directly into your PR template, so you don’t forget. It’s a lightweight way to keep speed in mind without slowing yourself down.

Track Performance Debt Like You Track Bugs

Sometimes we ship code that isn’t perfect maybe it’s a big query that’s not optimized, or we skipped lazy loading just to meet a deadline. That’s okay. Not everything needs to be perfect the first time.

But instead of ignoring it, write it down.

Create a simple markdown file like perf-debt.md or a shared document. Keep track of things like:

  • This page should use pagination

  • We can reduce unused font weights

  • This component re-renders more than necessary

Then, each sprint or dev cycle, fix one or two items. These aren’t urgent, but over time, these small improvements will have a big impact not just on speed, but on the maintainability of the codebase too.

Regular Check-ins Help You Stay Sharp

Once in a while, pause and look back at what we have improved and what still needs work.

We can keep a personal note or log with questions like:

  • What performance issue did I fix this month?

  • Is there a page that feels slower than it should?

  • What metric do I want to improve next?

Even just thinking about this once a month helps. It keeps performance in your awareness, without forcing it into every decision.

Track Performance Debt Like You Track Bugs

We will always have some trade-offs. Maybe you skipped optimization for a quick release. That is okay.

Just write it down. Keep a shared list in Notion or a markdown file like perf-debt.md. Examples might include:

  • List page should use pagination

  • Font files can be reduced

  • Too many renders on the dashboard

Each sprint, fix one or two. These small wins add up over time.

This kind of consistency is what makes performance sustainable. It’s not about heroic fixes or once a year audits. It’s about asking small questions during everyday coding and getting into the habit of solving small problems before they grow.


Conclusion

Performance doesn't stop at writing fast code it’s sustained by the systems around it. Background jobs, automation, monitoring, and everyday habits all play a part.

It’s not a one-time task but a long-term mindset.

By continuously asking: Can this be deferred? Offloaded? Automated? performance stays part of the process, not just a post-launch concern.

Keep improving in small ways, and the app will stay fast not just today, but as it grows.

0
Subscribe to my newsletter

Read articles from Savita Verma directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Savita Verma
Savita Verma

I'm a frontend developer trying to do more than just ship fast code. I care about clarity, purpose, and mindful work. Currently based in Bangalore and working my way toward becoming a senior dev. I write about the technical, the emotional, and everything in between. Feel free to reach out, I’m always excited to chat about frontend, tech, or opportunities. Email Id: svitaverma10@gmail.com