DEV Community

Dhiraj Shrotri
Dhiraj Shrotri

Posted on

How do you scale a frontend application to a million users ?

I was recently in a job interview (yes, the kind you have to do when you get laid off) and the interviewer asked me: "How would you scale a frontend application from 100,000 users to a million plus?"
I gave a decent enough answer at the moment. But afterwards, I kept thinking about everything I didn't say. So here we are. Consider this my full answer, delivered about three months too late, to actually matter for that job.

First, Let's Calibrate

Scaling a frontend isn't just about throwing more servers at the problem. That's your backend team's headache. The frontend has its own scaling concerns, and a lot of them are invisible until you're already on fire. The jump from 100k to 1M+ users is less about one dramatic architectural overhaul and more about fixing the 20% of things causing 80% of your slowness. The trick is knowing which 20%.

Performance & Asset Delivery: The Biggest Bang For Your Buck

If you're not serving your static assets through a CDN at this scale, stop reading this and go fix that first. Cloudflare, CloudFront, Fastly; pick one. Your origin server should never be sweating over a CSS file.

Pair your CDN with content-hashed filenames. This lets you set aggressive long-term cache TTLs without worrying about users getting stale assets after a deployment. It's one of those things that feels like a small detail until you realise it's quietly saving you a fortune in bandwidth costs and keeping your load times fast for repeat visitors.

The other non-negotiable is code splitting and lazy loading. Users should only download the JavaScript they need for the page they're actually on. Nobody visiting your landing page needs the bundle for your admin dashboard. With Vite this is largely handled for you at the route level, but you still need to be intentional about it.

State Management: Where Things Get Messy At Scale

At 100k users, messy state management is annoying. At a million plus, it becomes a performance crisis. The most important distinction to get right is separating server state from client state. These are fundamentally different things and treating them the same way is a trap I've fallen into personally.

Server state: data that lives on your backend and needs to be fetched, cached, and kept in sync, should be handled by something like React Query or SWR. These libraries give you stale-while-revalidate caching out of the box, which means your UI feels snappy while fresh data loads in the background.

Client state: things like UI toggles, modal open/close, user preferences etc belong in something lean like Redux or even just local component state if it doesn't need to be shared widely. Not everything needs to be in a global store. Redux is a perfectly good tool that a lot of people use as a hammer for every nail.

The API & Data Layer

A few principles that matter a lot at scale:

Virtualize large lists: Never render 10,000 rows in the DOM. Libraries like TanStack Virtual handle this elegantly, only the rows visible in the viewport actually exist in the DOM. This sounds obvious until you're staring at a performance profile wondering why scrolling feels like wading through concrete.

Debounce and throttle user-triggered requests: Search inputs that fire an API call on every keystroke are a classic scaling antipattern. Debounce them. Your backend team will thank you.

Paginate everything: Infinite scroll or traditional pagination - pick your poison, but loading entire datasets upfront is a sin at scale.

You Cannot Optimize What You Cannot Measure

This is probably the lesson I wish I'd learned earlier in my career. At scale, gut feel and local testing will actively mislead you. You need Real User Monitoring — tools like Datadog or Sentry that show you what actual users on actual devices on actual network conditions are experiencing, not just what your M2 MacBook on gigabit ethernet thinks is happening.

Track your Core Web Vitals ( LCP, CLS, and INP specifically): These aren't just Google SEO metrics, they directly correlate with whether users stay on your page or leave. If your LCP is over 2.5 seconds, a non-trivial percentage of users are already gone before they've seen anything meaningful.

Error tracking with context is also non-negotiable: At a million users, you will have errors you never saw in testing. You need to know about them before your users start tweeting about them.

Bundle Size: The Silent Killer

Run a bundle analysis on your app right now. I'll wait. Tools like rollup-visualizer (for Vite) or webpack-bundle-analyzer will show you a treemap of everything you're shipping to the user. There is almost always something in there that makes you say "why is that so big?"

Common culprits:

  • A date library you imported for one utility function that weighs 70kb.
  • A component library where you're importing the entire thing for three components.
  • Polyfills targeting browsers that represent 0.2% of your user base.

Tree shaking handles a lot of this automatically, but it's not magic. Named imports, side effectful modules, and certain CommonJS packages can defeat it. The bundle analyzer doesn't lie.

Architecture Patterns For The Long Game

Micro-frontends become a real conversation at a very large scale. Different teams owning different slices of the UI independently, deploying without coordinating with everyone else. The tradeoff is real though. Shared dependency management becomes its own full-time job and the coordination overhead can eat the productivity gains if you're not careful. I wouldn't reach for this unless you have multiple large teams stepping on each other's toes.

Edge rendering tools like Next.js Edge Runtime, Cloudflare Workers etc can dramatically reduce time-to-first-byte for a globally distributed user base. If your users are spread across continents and you're rendering from a single region, you're leaving a lot of performance on the table.

The Stuff Nobody Talks About Enough

Images. Consistently the most underestimated performance problem. Use modern formats (WebP, AVIF). Lazy load anything below the fold. Serve correctly sized images for each viewport. An image that looks fine on your screen might be a 4MB monster getting served to someone on mobile data.
Fonts. font-display: swap and preloading your critical fonts goes a long way. A font that blocks rendering is a font that's costing you users.

Third party scripts. Analytics tools, chat widgets, A/B testing libraries audit these ruthlessly. Every third party script is code you don't control running on your page, and at scale their performance cost adds up. Some of them are genuinely heavier than your entire application.

So, What's The Actual Answer?

If I could go back and answer that interview question more completely, I'd say this: scaling a frontend to a million users is mostly about discipline. Discipline in measuring before optimizing, discipline in keeping your bundle lean, discipline in not letting state management become a free-for-all, and discipline in treating performance as a feature rather than a cleanup task you'll get to eventually.

The technical solutions - CDNs, virtualization, code splitting, RUM - are well documented and not particularly exotic. The hard part is building a culture and a codebase where performance doesn't quietly degrade every sprint until you're suddenly wondering why conversion dropped 15%.

Anyway. If anyone's hiring a frontend engineer who thinks a lot about this stuff, you know where to find me.

Top comments (0)