Skip to content

Expert · Performance, security and testing · topic 28 of 34 · 30 min

Making the Notes app fast

Explanation

Performance means how quickly your app loads and reacts. First measure with Lighthouse, the DevTools Performance panel and data from real users. Then fix the biggest problem: heavy images and scripts on the frontend, slow queries and missing indexes on the backend. Always test on a cheap phone with slow 4G, because that is how many people in India will use your app.

Measure first, then fix

Performance is how fast your app feels: how soon the page shows something useful, how quickly it reacts to a tap, and whether things jump around while it loads. Guessing what is slow wastes time. You measure first, fix the biggest problem, and measure again.

Think of it like this: A good doctor checks your temperature and blood pressure before giving medicine. Lighthouse and DevTools are your thermometer. Changing code before you measure is like taking random tablets and hoping one works.
  • Lighthouse (in Chrome DevTools, Lighthouse tab): a lab test. It loads the page once on a simulated slow phone and gives scores and advice.
  • DevTools Performance panel: records what the browser did, second by second. Long yellow blocks are JavaScript that kept the page busy.
  • DevTools Network panel: shows every file, its size and how long it took. Use the throttling menu to choose "Slow 4G".
  • Real-user data (field data): numbers from real visitors. PageSpeed Insights shows Chrome user data for popular sites, and the small web-vitals library can send numbers from your own users to your analytics.

Core Web Vitals in 2026

Google uses three numbers, called Core Web Vitals, to describe the experience of a page. Each is judged at the 75th percentile of visits, which means 3 out of 4 visits should be "good". INP replaced the older FID metric in 2024.

MetricWhat it measuresGoodPoor
LCP (Largest Contentful Paint)Time until the biggest text or image on screen appears2.5 s or lessmore than 4 s
INP (Interaction to Next Paint)Time from a tap or key press until the screen updates200 ms or lessmore than 500 ms
CLS (Cumulative Layout Shift)How much things jump around while loading (no unit)0.1 or lessmore than 0.25
javascript
import { onLCP, onINP, onCLS } from 'web-vitals';

function send(metric) {
  // sendBeacon still works when the user closes the tab
  navigator.sendBeacon('/api/vitals', JSON.stringify({ name: metric.name, value: metric.value }));
}

onLCP(send);
onINP(send);
onCLS(send);
Runs in the browser: collect Core Web Vitals from real users of the Notes app.

Frontend fixes

  • Images: serve the size you show (a 400 px wide card does not need a 4000 px photo), use modern formats like WebP or AVIF, and always set width and height so the layout does not jump (better CLS).
  • Lazy loading: add loading="lazy" to images further down the page so they load only when the user scrolls near them.
  • Code splitting: load the code for a screen only when the user opens it, for example with import() or your framework's dynamic loading.
  • Fewer fonts: one or two font families, only the weights you use, with font-display: swap so text shows at once.
  • Caching static files with a CDN: a CDN (Content Delivery Network) keeps copies of your JS, CSS and images on servers near users, for example in Mumbai or Chennai. Give files a hash in the name and a long cache time.
html
<!-- Hero image at the top: load it early, never lazy -->
<img src="/img/hero-800.webp" width="800" height="450" fetchpriority="high" alt="Notes app on a phone">

<!-- Images further down: lazy, with sizes for small and big screens -->
<img src="/img/share-400.webp"
     srcset="/img/share-400.webp 400w, /img/share-800.webp 800w"
     sizes="(max-width: 600px) 100vw, 400px"
     width="400" height="300" loading="lazy" alt="Sharing a note">
Width and height stop layout jumps; srcset lets a phone pick the small file.

Backend fixes

  • Database indexes: an index is a sorted lookup list, like the index at the back of a book. The notes list filters by user_id and sorts by updated_at, so index those columns together.
  • Avoid N+1 queries: N+1 means one query for a list, then one more query for every item in it. Fetch everything you need in one query with a JOIN or WHERE id IN (...).
  • Pagination: return 20 notes at a time with LIMIT, not all 3,000.
  • Caching: keep the answer to an expensive question for a short time instead of working it out on every request.
  • Compression: turn on gzip or Brotli so JSON and HTML travel smaller over the network. Most hosts and CDNs do this for you.
sql
-- The query behind GET /api/notes
SELECT id, title, pinned, updated_at
FROM notes
WHERE user_id = ?
ORDER BY pinned DESC, updated_at DESC
LIMIT 20 OFFSET 0;

-- One index makes it fast even with millions of notes
CREATE INDEX idx_notes_user_updated ON notes (user_id, updated_at);
Filter, sort and limit in the database, with an index that matches the query.

Design for a slow 4G phone

Many users in India browse on budget Android phones with patchy 4G, often in a train or a crowded market. A page that feels instant on your laptop on office Wi-Fi can take 10 seconds there. In DevTools, set the network to "Slow 4G" and the CPU to "4x slowdown" before you judge your app.

Tip: Set a performance budget and check it on every change, for example: JavaScript under 170 KB compressed, the largest image under 150 KB, LCP under 2.5 s on Slow 4G. A budget turns "make it fast" into a rule you can test.
Careful: Do not lazy-load the main image at the top of the page: it delays LCP. And do not trust a single Lighthouse run on your fast laptop. Run it a few times, with throttling on, and compare with real-user data.

Watch the lesson

Making the Notes app fast · 1/6

Measure -> fix the biggest problem -> measure again

A slow app loses users. Today we make the Notes app fast, the right way: measure first.

Prefer a video by a teacher? Find free videos on “core web vitals LCP INP CLS explained web performance tutorial” on YouTube ↗

In real life: A college results site on results day

On results day, lakhs of students open the same results site at 10 am, mostly on phones. A team that measured first would see that the home page loads a 2 MB banner image and 900 KB of JavaScript for a carousel nobody uses. They compress the banner to WebP at 80 KB, remove the carousel, add an index on roll_number in the results table and cache the static files on a CDN. The same servers now handle the rush, and students on slow 4G see their marks in 2 seconds instead of 12.

Practice (0/3 done)

Task 1 · Code · Javascript

Write rateVitals({ lcp, inp, cls }) that returns { lcp, inp, cls } where each value is "good", "needs improvement" or "poor". lcp and inp are in milliseconds. LCP: good if <= 2500, poor if > 4000. INP: good if <= 200, poor if > 500. CLS: good if <= 0.1, poor if > 0.25. Anything between is "needs improvement".

Task 2 · Code · Javascript

The Notes search box calls the API on every key press. Write debounce(fn, wait, scheduler) that returns a new function. Each call cancels the previous waiting call and schedules fn to run after wait ms with the latest arguments. Use scheduler.setTimeout(callback, ms), which returns an id, and scheduler.clearTimeout(id) (the tests pass a fake clock so no real time passes).

Task 3 · Practice

Run a performance check on your deployed Notes app (or any site you built). Write a short report with numbers before and after one fix.

Quick quiz

Question 1

1.What should you do before changing code to make an app faster?

Question 2

2.A page has an INP of 350 ms. How is it rated?

Question 3

3.Which change most directly reduces CLS (layout shift)?

Question 4

4.The notes list runs one query for the list, then one query per note to get its tags. What is this problem called?

Question 5

5.Why test your app with "Slow 4G" and CPU slowdown in DevTools?

Read more (free)

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

← Previous: Monitoring, logs and alerts