Expert · Performance, security and testing · topic 28 of 34 · 30 min
Making the Notes app fast
Explanation
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.
- 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.
| Metric | What it measures | Good | Poor |
|---|---|---|---|
| LCP (Largest Contentful Paint) | Time until the biggest text or image on screen appears | 2.5 s or less | more than 4 s |
| INP (Interaction to Next Paint) | Time from a tap or key press until the screen updates | 200 ms or less | more than 500 ms |
| CLS (Cumulative Layout Shift) | How much things jump around while loading (no unit) | 0.1 or less | more than 0.25 |
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);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.
<!-- 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">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.
-- 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);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.
Watch the lesson
Making the Notes app fast · 1/6
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)
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".
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).
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
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.