Skip to main content

Command Palette

Search for a command to run...

🚀 Optimising Performance in a Full-Stack App

Published
•3 min read
🚀 Optimising Performance in a Full-Stack App

Onboarding

When I first built my full-stack app, I was proud that it worked. The frontend displayed data, the backend served it, and the database stored it. But soon, I noticed something strange:

  • The app loaded slower as more data piled up.

  • API calls were taking longer than expected.

  • Images made the UI feel heavy.

That’s when I realised — building a functional app is just the beginning. To make it truly usable, you need performance optimisation.

This week, I explored three powerful techniques: caching, pagination, and lazy loading.


🧩 Caching: Don’t Repeat Yourself (or the Server)

At first, every time my React app requested data, the backend fetched it fresh from the database. This was fine for 10 requests… but not for 1,000.

What I Learned

Caching stores frequently used data temporarily, so the app doesn’t have to fetch it again and again.

My Fix

In Node.js + Express, I added a simple in-memory cache using node-cache:

npm install node-cache
import NodeCache from "node-cache";
const cache = new NodeCache({ stdTTL: 60 }); // cache for 60 seconds

app.get("/tasks", (req, res) => {
  const cachedData = cache.get("tasks");
  if (cachedData) {
    return res.json(cachedData); // return cached result
  }

  Task.find().then((tasks) => {
    cache.set("tasks", tasks);
    res.json(tasks);
  });
});

Result → Repeat API calls got instant responses ⚡


📄 Pagination: Don’t Serve the Whole Buffet at Once

My database kept growing, and I was still sending all the records to the frontend. The browser struggled to render them, and the UI lagged.

What I Learned

Pagination is like serving food in small plates instead of dumping everything at once.

My Fix

On the backend:

app.get("/tasks", async (req, res) => {
  const page = parseInt(req.query.page) || 1;
  const limit = 5;
  const skip = (page - 1) * limit;

  const tasks = await Task.find().skip(skip).limit(limit);
  res.json(tasks);
});

On the frontend:

axios.get(`http://localhost:5000/tasks?page=${page}`)
  .then(res => setTasks(res.data));

Result → My UI became snappy because it only handled a few records at a time.


🖼 Lazy Loading: Show Only What’s Needed

The biggest culprit of slowness? Images. My frontend tried to load every image at once, even if they were way below the fold.

What I Learned

Lazy loading loads only what the user sees. Extra content loads on demand as they scroll.

My Fix (React Example)

<img 
  src="thumbnail.jpg" 
  loading="lazy" 
  alt="Task Preview" 
/>

Or using a library like react-lazyload:

npm install react-lazyload
import LazyLoad from "react-lazyload";

<LazyLoad height={200}>
  <img src="big-image.jpg" alt="Optimized" />
</LazyLoad>

Result → Faster first render, smoother scrolling.


⚡ Challenges

  • Over-caching: I cached too aggressively and sometimes showed outdated data. The fix? Balance TTL (time-to-live).

  • Pagination bugs: At first, I forgot to handle “no more data” properly, which caused infinite blank pages.

  • Lazy loading flicker: Some images showed empty space before loading — I solved this by adding placeholder loaders.


🎯 Takeaways

This week taught me that performance is not just about code correctness, but user experience:

  • Caching = Speed up repeat requests.

  • Pagination = Avoid overwhelming the frontend.

  • Lazy Loading = Prioritise what the user sees first.

Now, my app feels lighter, faster, and more professional. The difference is like night and day.

On to the next challenge 🚀