LogoTRUONG PHAM
Home
Projects
Blogs
YouTube
Contact

Newsletter

Stay updated with technical artifacts and engineering insights.

LogoTRUONG PHAM

Building scalable software and sharing insights on technology & life.

Sitemap

  • Home
  • Projects
  • Blogs
  • YouTube
  • Contact

Connect

  • GitHub
  • LinkedIn
  • Email
  • YouTube

© 2024 TRUONG PHAM. © All rights reserved.

Privacy PolicyTerms of Service
Back
Blog #2: The 60 FPS Illusion – When your MacBook Pro 'deceives' your senses
50 FRONTEND LESSONS – HARD-EARNED EXPERIENCES

Blog #2: The 60 FPS Illusion – When your MacBook Pro 'deceives' your senses

The story of a dashboard that looked incredibly smooth on the dev machine but became a 'laggy' nightmare on real users' devices.

TP
Truong PhamSoftware Engineer
PublishedMarch 12, 2024

I used to have a MacBook Pro M1 Max. It was a performance beast. And it was that very machine that indirectly led me to almost have a client cancel a contract two years ago.

At the time, I was finalizing a logistics management dashboard with thousands of rows of data and continuous real-time charts. Locally, everything loaded instantly. Scrolling was so smooth I confidently claimed: "This is the fastest web app I've ever written." But when it was deployed for warehouse staff—users with old Android phones and shaky 4G connections—they responded with a screen recording: the app took 10 seconds to load, and every time they scrolled, it froze for 2 seconds.

It felt like being splashed with a bucket of cold water in the middle of winter. I suddenly realized I was living in a technological "bunker" far removed from reality.

1. The Silent Enemies: Network Latency and CPU Throttling

Locally, you access code via localhost, with almost zero network latency. But on Production, every JS file, every image has to travel through thousands of miles of fiber optics, through routers, and sometimes weak 4G towers.

Even more important is the story of the CPU. Your computer can process millions of calculations in a heartbeat, but a browser on a mid-range phone will become a bottleneck as soon as it has to "chew" through a 2MB JavaScript file. When that happens, the Main Thread is completely occupied with processing the JS, preventing the UI from responding to user interactions. That is the feeling of "lag" we often talk about.

2. The Problem: "Death" by 10,000 table rows

My dashboard at the time had a very basic flaw that was hidden by the power of the M1 chip: I was rendering all 10,000 rows of data into the DOM at once.

// My naivety: "My machine is strong, why worry!"
const InventoryTable = ({ data }) => {
  return (
    <table>
      {data.map(item => (
        <Row key={item.id} {...item} />
      ))}
    </table>
  );
};

On my machine, 10,000 DOM nodes were nothing. But on Prod, when users filtered data, React had to perform the Diffing and Re-render process for that entire "sea" of data. The users' phones heated up, and browsers froze. Worse, product images were unoptimized, 5MB each, draining their 4G data plans.

3. How I handled it: Learning to "play poor" to debug

I started the debugging process with a completely different mindset. Instead of just running the code, I used Chrome DevTools and performed two "self-inflicted" steps:

  1. Network Throttling: Set to "Fast 3G".
  2. CPU Throttling: Set to "6x slowdown".

And "Boom!". The truth was revealed. My dashboard shattered. It took 12 seconds just to see the first line. This was when I used the React Profiler to discover "harmless" components that were re-rendering thousands of times pointlessly.

The final solution:

  • Virtualization: I used react-window to render only the 20 rows currently visible on screen instead of all 10,000.
  • Image Optimization: Converted all images to WebP and used a CDN to resize them to their exact display size.
  • Code Splitting: Split off heavy charts and only loaded them when the user actually scrolled to them.

4. The Trade-offs

Applying Virtualization isn't a "silver bullet." It increases code complexity significantly. Calculating row heights (if they aren't uniform) is a logic nightmare. Additionally, the browser's default search (Ctrl + F) won't work because the data isn't actually in the DOM. I had to write a custom search bar to compensate for this downside.

If I were to do it from scratch, I would design the system to use Pagination or Infinite Scroll starting from the API layer, rather than trying to pull all the data to the Client and then figuring out how to optimize its display.

5. Lesson Learned: Don't measure with your own machine

This incident taught me that: Performance is a feature, not an afterthought. A beautiful app that lags is still a bad app.

My advice to you:

  • Always test with the weakest device possible: If your app runs smoothly on a $150 Android phone, it will run great on an iPhone 15 Pro. The reverse is not guaranteed.
  • Beware of external libraries: Every import is a bandwidth burden. Ask yourself: "Do I really need this 100kb library just for one icon?".
  • Run Lighthouse frequently: Don't wait until deployment to run the Lighthouse report. Run it during the development process.

My post-optimization build reduced load time from 12 seconds to 1.5 seconds on 3G. The client's eyes changed from anger to admiration. But deep down, I knew I owed them an apology for my technological arrogance.


Don't let your MacBook become a wall between you and your users.

Series • Part 2 of 50

50 FRONTEND LESSONS – HARD-EARNED EXPERIENCES

NextBlog #3: Infinite Loops – When useEffect turns the browser into a 'furnace'
Blog #1: When 'It works on my machine' is the sweetest lie
01Blog #1: When 'It works on my machine' is the sweetest lie02Blog #2: The 60 FPS Illusion – When your MacBook Pro 'deceives' your sensesReading03Blog #3: Infinite Loops – When useEffect turns the browser into a 'furnace'04Blog #4: Hydration Error – When SSR's perfection 'slaps' me in the face at midnight05Blog #5: 200 OK – The system's sweet little lies06Blog #6: Why you shouldn't fully trust console.log – The silent deceiver07Blog #7: The 'Double' Nightmare – When React StrictMode tests your patience
TP

Written by Truong Pham

Software Engineer passionate about building high-performance systems and meaningful experiences.

Read more articles