Instant Navigation in Web Apps: A Practical Guide Based on GitHub Issues Modernization

By ✦ min read

Introduction

When you're working through a backlog—opening an issue, jumping to a linked thread, then back to the list—latency isn't just a metric. It's a context switch. Even small delays add up, and they hit hardest at the exact moments developers are trying to stay in flow. GitHub Issues wasn't slow in isolation, but too many navigations paid the cost of redundant data fetching, breaking flow repeatedly. This guide shows you how to eliminate that perceived latency using the techniques we applied to modernize GitHub Issues: client-side caching with IndexedDB, a preheating strategy, and a service worker. You'll learn step by step how to replicate this pattern in your own data-heavy web app, turning slow navigations into instant ones.

Instant Navigation in Web Apps: A Practical Guide Based on GitHub Issues Modernization
Source: github.blog

What You Need

Step-by-Step Guide

Step 1: Define and Measure the Right Metric

Don't optimize raw server response time alone. Instead, focus on perceived latency—the time from user action (click, tap) to seeing meaningful content. For GitHub Issues, we targeted the time between clicking an issue link and seeing the issue detail page render with previously cached data. Use tools like Lighthouse or Performance API to measure 'First Paint with cached content' vs. 'Full load'. Set a goal: sub-100ms for instant feel.

Step 2: Build a Client-Side Caching Layer with IndexedDB

Store frequently accessed data locally so navigations don't always hit the network. Create an IndexedDB database with object stores for your data (e.g., issues, comments). Use a key-value pattern: key = entity ID, value = JSON blob. Update the cache on every successful fetch. For GitHub Issues, we cached issue details, list data, and metadata. Ensure you handle cache expiry (e.g., 5 minutes) and size limits (e.g., 50MB).

// Example: Caching issue data
async function cacheIssue(issueId, data) {
  const db = await openDB('issues-cache', 1);
  const tx = db.transaction('issues', 'readwrite');
  await tx.store.put({ id: issueId, data, timestamp: Date.now() });
  await tx.done;
}

Step 3: Implement a Preheating Strategy

Preheating means proactively fetching data likely to be needed soon, without user request. Based on user behavior (e.g., hovering over a link, scrolling through a list), prefetch the data and store it in IndexedDB. This improves cache hit rates. For Issues, when a user views a list, we prefetch details for the first few issues. Use requestIdleCallback or a small timeout to avoid jank. Be careful not to spam requests—use a priority queue.

Step 4: Introduce a Service Worker for Hard Navigations

Hard navigations (full page reloads) bypass client-side JavaScript cache. Register a service worker that intercepts fetch requests and serves cached data from IndexedDB if available. For GitHub Issues, the service worker listens to navigation requests for issue pages, checks the cache, and returns cached HTML or JSON. If not cached, it falls back to network and updates the cache. This makes even reloads feel instant.

// Service worker: serve cached issue page
self.addEventListener('fetch', (event) => {
  if (event.request.mode === 'navigate') {
    event.respondWith(serveFromCacheOrFallback(event.request));
  }
});

Step 5: Revalidate in Background

Show cached data instantly, then fetch fresh data from the server in the background. This is key for perceived instantness. In your component, when a navigation occurs: (1) render from IndexedDB immediately, (2) fire a fetch request to the server, (3) update the cache and re-render if data changed. This pattern—stale-while-revalidate—ensures the user sees content immediately while staying up to date. Implement with a simple state machine: LOADING (from cache), READY (display cached), UPDATING (background fetch), FINAL (updated).

Instant Navigation in Web Apps: A Practical Guide Based on GitHub Issues Modernization
Source: github.blog

Step 6: Monitor and Iterate

Use real-user monitoring (RUM) to track perceived latency. GitHub Issues measured improvement in 'navigation time to interactive' from ~800ms to under 100ms. Set up alerts for cache miss rates and fallback times. A/B test different preheating strategies. Iterate based on usage patterns. Also be mindful of tradeoffs: increased memory usage, stale data risks, and complexity. Document your cache invalidation rules clearly.

Tips for Success

By following these steps, you can transform a slow, data-heavy web app into one that feels instant. The techniques used on GitHub Issues are directly transferable—you don't need a full rewrite. Start small, measure impact, and iterate. Your users will thank you.

Tags:

Recommended

Discover More

Crisis in Classrooms: One in Seven Teachers Set to Quit as Conditions DeteriorateTux the Penguin at 30: Celebrating Linux's Beloved MascotClaude AI Security Blind Spots: The Confused Deputy Problem Across Three Attack SurfacesSecuring Your Organization in the Age of AI-Powered Vulnerability DiscoveryAssessing the Appeal of an AI-Powered Phone: A Decision-Making Guide