Rendering Huge Pull Requests in the GitHub Copilot App:

Rendering Huge Pull Requests in the GitHub Copilot App


By Alberto Gimeno, Principal Design Engineer at GitHub (@gimenete)




The Challenge of Massive Diffs


Modern pull requests can contain thousands of changed lines across hundreds of files. Rendering these efficiently in a desktop-class application—like the GitHub Copilot app—presents unique performance challenges: DOM bloat, memory pressure, and sluggish scrolling. In 2026, with AI-assisted coding accelerating development velocity, PRs are larger and more frequent than ever. This article details how we approached rendering huge pull requests without sacrificing responsiveness.


Why React Alone Isn’t Enough


A naive implementation would render every line of every diff into the DOM. For a PR with 10,000+ lines, this leads to:

  • Layout thrashing and janky scrolling.
  • Excessive memory usage (each DOM node carries overhead).
  • Slow initial paint, even with React.memo and useMemo.

We needed a solution that decouples the logical diff model from the rendered viewport.


Key Strategies


1. Virtualized Rendering with Dynamic Heights


We adopted a windowing approach: only rows (files, hunks, or lines) within the visible viewport (plus a small overscan buffer) are mounted. Because diff lines can vary in height (wrapped code, comments, inline suggestions from Copilot), we compute heights on the fly and cache them in a virtual list. This keeps the DOM node count under ~500 regardless of PR size.


2. Chunked Diff Parsing Off the Main Thread


Parsing a unified diff into structured hunks is CPU-intensive. We offload this to a Web Worker, streaming results back in chunks. The main thread receives partially parsed data and can render progressively—users see the first files almost instantly, while the rest load in the background.


3. Incremental Hydration of Syntax Highlighting


Syntax highlighting is expensive. Instead of highlighting all lines upfront, we apply it lazily per visible chunk, using a lightweight tokenizer that caches results per line. For unchanged lines (context), we reuse pre-computed tokens from a global cache keyed by line content and language.


4. Memory-Aware Caching


We maintain an LRU cache of parsed hunks and highlighted lines. When memory pressure exceeds a threshold (monitored via performance.memory in Chromium-based runtimes), we evict the least recently used entries. This prevents unbounded growth during long review sessions.


5. Smooth Scrolling with `content-visibility` and `contain`


For parts of the diff that are off-screen but still in the DOM (e.g., a large file just scrolled past), we apply CSS content-visibility: auto and contain: strict to skip rendering work. Combined with virtualization, this yields near-60fps scrolling even on low-end machines.


Results


  • Initial render time for a 20,000-line PR dropped from ~12s to under 800ms.
  • Memory usage stabilized at ~150MB even after reviewing 50+ large PRs in a session.
  • Scroll performance remained at 60fps (or the display’s refresh rate) across all tested devices.

Lessons Learned


  • Measure early: Use React Profiler and Chrome Tracing to identify bottlenecks before optimizing.
  • Offload aggressively: Web Workers are your friend for parsing, diffing, and even highlighting.
  • Cache with bounds: Unbounded caches are memory leaks waiting to happen.
  • Progressive disclosure: Rendering a skeleton and filling in details feels faster than a blank screen.

Conclusion


Rendering huge pull requests is a solvable problem with the right architecture. By combining virtualization, off-main-thread parsing, and memory-aware caching, the GitHub Copilot app delivers a responsive review experience even for the largest AI-generated PRs. As we move into 2026, these techniques will remain essential as codebases and collaboration scales continue to grow.




Alberto Gimeno is a Principal Design Engineer at GitHub. You can follow him on GitHub.

via GitHub AI Blog

Related