DEV Community

Weather Clock Dash
Weather Clock Dash

Posted on

5 Ways Firefox Extension New Tab Pages Are Killing Your Browser Performance

The Hidden Performance Cost of New Tab Extensions

You install a new tab extension to make your browser nicer. You get weather, a clock, a background image. But then you notice Firefox feels... slower. The new tab hesitates before loading. Your CPU spikes for half a second.

Here's what's probably happening — and how the good extensions avoid it.

Problem 1: Synchronous localStorage Access on Startup

Every new tab load starts fresh. Your extension needs to load settings. The naive approach:

// Slow: synchronous, blocks rendering
const settings = JSON.parse(localStorage.getItem('settings'));
applyTheme(settings.theme);
renderClock(settings.timezone);
Enter fullscreen mode Exit fullscreen mode

This is synchronous, which means the browser can't paint anything until localStorage.getItem returns. On slower storage, this adds measurable lag.

The fix:

// Async: browser can start painting while we load settings
async function init() {
  const settings = await loadSettings();
  applyTheme(settings.theme);
}

// Meanwhile, show defaults immediately
showDefaultState();
init();
Enter fullscreen mode Exit fullscreen mode

Show a default state immediately, then update when settings load. Users see something instantly.

Problem 2: Blocking on API Calls Before Rendering

Weather extensions that look up your location and fetch weather before rendering anything are painful:

// Terrible: blocks the entire page
async function badInit() {
  const position = await getLocation();      // 50-500ms
  const weather = await fetchWeather(position); // 200-800ms
  renderEverything(weather);
}

badInit(); // User sees blank screen for 250ms-1.3 seconds
Enter fullscreen mode Exit fullscreen mode

The fix: Cache aggressively, render stale data immediately, update in background:

function goodInit() {
  // Render cached data immediately (zero wait)
  const cached = getCachedWeather();
  if (cached) renderWeather(cached);
  else renderWeatherPlaceholder();

  // Update in background (non-blocking)
  refreshWeatherInBackground();
}
Enter fullscreen mode Exit fullscreen mode

Problem 3: Re-fetching on Every New Tab

If every new tab triggers a fresh API call, you're making potentially dozens of requests per hour. Weather APIs often throttle you, and even if they don't, the round-trip latency adds up.

The solution: cache with a TTL.

const CACHE_TTL = 30 * 60 * 1000; // 30 minutes

async function getWeather() {
  const cached = JSON.parse(localStorage.getItem('weatherCache') || 'null');

  if (cached && (Date.now() - cached.timestamp) < CACHE_TTL) {
    return cached.data; // Instant: no network request
  }

  const fresh = await fetchFromAPI();
  localStorage.setItem('weatherCache', JSON.stringify({
    data: fresh,
    timestamp: Date.now()
  }));
  return fresh;
}
Enter fullscreen mode Exit fullscreen mode

This reduces API calls from ~30/hour to ~2/hour for a typical user.

Problem 4: Loading Unnecessary Resources

Look at the network waterfall for your new tab extension. You might see:

  • 3 Google Fonts files
  • A CSS framework (Bootstrap at 150KB?)
  • Several icon libraries
  • Analytics scripts from 4 vendors

All of these add load time. For a new tab page, every 100ms of extra load time is felt because new tabs open constantly throughout the day.

The aggressive approach: use system fonts, inline critical CSS, load everything else lazily or not at all.

<!-- Instead of this -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter">

<!-- Use this -->
<style>
  body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  }
</style>
Enter fullscreen mode Exit fullscreen mode

System fonts: zero network requests, renders instantly, looks great on every OS.

Problem 5: Clock Drift from setInterval

Displaying a clock with setInterval(updateClock, 1000) seems fine, but it has a subtle problem: intervals drift. After an hour, your clock might be off by seconds. And each setInterval fires even if the tab is in the background, wasting CPU.

// Naive (drifts over time)
setInterval(() => {
  document.getElementById('clock').textContent = 
    new Date().toLocaleTimeString();
}, 1000);
Enter fullscreen mode Exit fullscreen mode

The fix: sync to real time with setTimeout:

function updateClock() {
  const now = new Date();
  document.getElementById('clock').textContent = 
    now.toLocaleTimeString();

  // Calculate ms until next second
  const delay = 1000 - (now.getMilliseconds());
  setTimeout(updateClock, delay);
}

updateClock(); // Start synced to real time
Enter fullscreen mode Exit fullscreen mode

This fires exactly as each new second begins — no drift, no CPU waste.

What Good New Tab Performance Looks Like

A well-optimized new tab extension should:

  • Render something in < 16ms (within one frame)
  • Show full UI in < 100ms (with cached data)
  • Make zero network requests for the first render
  • Run zero setInterval (use setTimeout sync instead)
  • Load < 10KB of CSS (use system fonts, inline critical styles)

Real-World: The Weather & Clock Dashboard

I built the Weather & Clock Dashboard for Firefox applying these principles:

  • Settings load from localStorage with a graceful default fallback
  • Weather is cached for 30 minutes and rendered stale-while-revalidating
  • Zero external fonts — uses system font stack
  • Clock uses setTimeout sync for accurate, drift-free display
  • No analytics, no tracking scripts

Result: the new tab opens instantly — visually complete before you've moved your fingers from the keyboard shortcut.

If you're building a new tab extension (or any browser extension), these patterns matter. The new tab is opened constantly — it needs to be fast.


Are there other performance issues you've noticed in new tab extensions? Drop them in the comments.

Top comments (0)