DEV Community

Cover image for Slashing Network Payload by 76%: How I Used Sentry and Gemini to Optimize a Real-Time Streaming Platform
Pritam Patra
Pritam Patra

Posted on

Slashing Network Payload by 76%: How I Used Sentry and Gemini to Optimize a Real-Time Streaming Platform

Summer Bug Smash: Clear the Lineup 🐛🛹

From 20+ Unnecessary Renders to a 76.5% Smaller Network Payload — How I Optimized Rawbee with Sentry + Gemini

This is a submission for DEV's Summer Bug Smash: Clear the Lineup powered by Sentry.

Project Overview

Project: Rawbee — Collaborative Real-Time Streaming Platform
Implementation: View the GitHub Pull Request
Repository: View the GitHub Repository

Rawbee is a full-stack video sharing and live streaming platform with traditional VOD features (uploads, subscriptions, watch history) alongside a WebRTC-based collaborative streaming engine for co-streaming, screen sharing, and in-browser voice/video chat.

The current stack includes:

  • Frontend: React 18, TypeScript, Tailwind CSS, Vite
  • Backend: Node.js, Express
  • Database: MongoDB
  • Media: Cloudinary
  • Observability: Sentry
  • AI debugging assistant: Google Gemini

For the Summer Bug Smash, I focused on improving the existing application rather than adding a new feature.

My goal was simple:

Find real problems → measure them → understand the root cause → fix them → measure again.

During the Bug Smash, I addressed frontend performance issues, a backend reliability problem, and runtime errors surfaced through Sentry.


Bug Fix or Performance Improvement

As Rawbee grew, the homepage started doing more work than necessary.

The first issue appeared while using the search bar. Updating the search state caused the parent component to re-render, and the video-card grid was repeatedly evaluated even when individual video data had not changed. React Profiler confirmed repeated VideoCard renders during the interaction, which led me to investigate whether those renders were actually necessary.

The second problem was visible directly in Chrome DevTools. The homepage was transferring a surprisingly large amount of data, with several individual image assets reaching hundreds of kilobytes.

The original production trace contained images such as:

834 KB PNG
433 KB JPEG
160 KB JPEG
Enter fullscreen mode Exit fullscreen mode

I also found that the frontend was loading more JavaScript up front than necessary because page-level code was not being split efficiently.

On the backend, MongoDB was failing to connect in my Windows environment with:

querySrv ECONNREFUSED
Enter fullscreen mode Exit fullscreen mode

Finally, Sentry was capturing frontend runtime errors that gave me useful visibility into failures occurring inside the application.

Controlled Before/After Benchmark

To make the performance comparison meaningful, I benchmarked the before and after production builds using the same setup:

  • Chrome
  • Fast 4G throttling
  • Browser cache disabled
  • Same homepage
  • Same machine
  • Same production preview workflow
Metric Before After Improvement
Network transferred 2.0 MB 471 KB 76.5% reduction
Resources loaded 2.3 MB 767 KB 66.7% reduction
Load event 2.61 s 1.60 s 38.7% faster
Finish 2.82 s 1.81 s 35.8% faster
Requests 30 32 No improvement
DOMContentLoaded 593 ms 624 ms Slightly higher

I am deliberately reporting the metrics that actually improved instead of dressing every measurement as a win. Request count went slightly up, partly because of a stale thumbnail returning 404, and DOMContentLoaded shifted marginally within measurement noise.

The headline result is the 76.5% payload reduction.
The biggest measurable gain came from reducing the amount of image data transferred to the browser.

Before: Production Benchmark

Chrome DevTools Network panel showing the unoptimized production build with 30 requests, 2.0 MB transferred, 2.3 MB of resources, and a 2.61 second Load time

Production baseline before optimization. The homepage transferred approximately 2.0 MB, dominated by unoptimized image assets — including an 834 KB PNG, a 433 KB JPG, and a 160 KB JPG.

After: Production Benchmark

Chrome DevTools Network panel showing the optimized production build with reduced image payload and improved loading performance

Same page, same throttling, same cache-disabled conditions. Payload dropped from 2.0 MB to 471 KB and Load time from 2.61 s to 1.60 s.


Code

The complete implementation is available in the GitHub Pull Request.

The main changes covered:

React.memo
Cloudinary image transformations
React.lazy / Suspense
MongoDB connection workaround
Sentry configuration improvements
Enter fullscreen mode Exit fullscreen mode

1. Memoizing VideoCard

import { memo } from "react";

const formatDuration = (seconds: number) => {
  // ...
};

const formatViews = (views: number) => {
  // ...
};

const VideoCard = memo(function VideoCard({
  video,
}: VideoCardProps) {
  return (
    <div className="group cursor-pointer">
      {/* Video card UI */}
    </div>
  );
});

export default VideoCard;
Enter fullscreen mode Exit fullscreen mode

The important optimization here is React.memo.

When the parent component updates, React can skip rendering the memoized VideoCard if the video prop remains referentially equal.

I also moved pure formatting helpers outside the component so they are not recreated as part of each component execution.

2. Cloudinary Image Optimization

This was the single biggest performance win in the benchmark. An 834 KB PNG for a card thumbnail was far larger than necessary for the displayed dimensions.

const getOptimizedCloudinaryUrl = (
  url: string,
  width = 500
) => {
  if (!url || !url.includes("cloudinary.com")) {
    return url;
  }

  return url.replace(
    "/upload/",
    `/upload/w_${width}/f_auto/q_auto/`
  );
};
Enter fullscreen mode Exit fullscreen mode

Then:

<img
  src={getOptimizedCloudinaryUrl(video.thumbnail, 500)}
  alt={video.title}
/>
Enter fullscreen mode Exit fullscreen mode

The w_500 resizes to display dimensions, f_auto serves modern formats (WebP/AVIF) where supported, and q_auto picks quality intelligently.

The before trace contained image responses of 834 KB, 433 KB, and 160 KB. The after trace shows the same images at 7.3 KB, 2.0 KB, 22.0 KB, and 2.6 KB. This single change is the primary contributor to the 2.0 MB → 471 KB reduction.

3. Route-Level Code Splitting

import { lazy, Suspense } from "react";

const Home = lazy(() => import("./pages/Home"));
const Login = lazy(() => import("./pages/Login"));

function App() {
  return (
    <Router>
      <Suspense
        fallback={
          <div className="flex h-screen items-center justify-center">
            Loading application...
          </div>
        }
      >
        <Routes>
          <Route path="/login" element={<Login />} />
          <Route path="/" element={<Home />} />
        </Routes>
      </Suspense>
    </Router>
  );
}
Enter fullscreen mode Exit fullscreen mode

This moved page-specific JavaScript into route-level chunks instead of requiring every page to be part of the initial route workload.

4. Sentry Environment Configuration

// frontend/.env

+ VITE_SENTRY_DSN=your_dsn_here
Enter fullscreen mode Exit fullscreen mode
// frontend/src/main.tsx

Sentry.init({
-  dsn: "your_dsn_here",
+  dsn: import.meta.env.VITE_SENTRY_DSN,
});
Enter fullscreen mode Exit fullscreen mode

This makes the configuration easier to manage across different environments.

5. MongoDB DNS Failure

The backend was failing with:

querySrv ECONNREFUSED
Enter fullscreen mode Exit fullscreen mode

I investigated the DNS behavior in the Windows environment and used:

Resolve-DnsName <mongodb-host>
Enter fullscreen mode Exit fullscreen mode

to verify the DNS resolution problem.

I then switched to a direct MongoDB connection configuration as a workaround for the affected environment, restoring database connectivity.


My Improvements

1. Eliminating unnecessary React work

The first performance problem was component-level rendering.

A search update causes the Home component to update, but an individual video card does not necessarily need to update with it.

Before optimization:

Search state changes
        ↓
Home updates
        ↓
VideoCard repeatedly renders
Enter fullscreen mode Exit fullscreen mode

After optimization:

Search state changes
        ↓
Home updates
        ↓
React.memo checks props
        ↓
VideoCard render skipped when props are unchanged
Enter fullscreen mode Exit fullscreen mode

React Profiler Evidence

React Profiler evidence — before:

React DevTools Profiler showing VideoCard rendering repeatedly during the unoptimized search interaction

Before memoization: VideoCard renders repeatedly across the profiling session as parent state updates.

React Profiler evidence — after:

React DevTools Profiler showing VideoCard2 as a memoized component that did not render during the optimized interaction

After memoization: the Profiler explicitly reports "Did not render on the client during this profiling session." The memoized component is skipped entirely when props are unchanged.

React Profiler evidence for the memoized component. The optimized VideoCard was skipped during the recorded interaction when its props remained unchanged.

I am intentionally not presenting the React commit count itself as the number of renders. The important result is that the profiler directly shows the memoized component being skipped.

2. Reducing image payload

The largest measurable performance improvement came from image delivery.

The original production trace contained image resources such as:

834 KB
433 KB
160 KB
Enter fullscreen mode Exit fullscreen mode

After applying Cloudinary transformations, the optimized trace contained much smaller responses, including examples around:

7.3 KB
2.0 KB
22.0 KB
2.6 KB
Enter fullscreen mode Exit fullscreen mode

This change was the main contributor to:

2.0 MB → 471 KB
Enter fullscreen mode Exit fullscreen mode

or a 76.5% reduction in transferred data.

3. Reducing initial JavaScript work

The route-level React.lazy() change means that the browser no longer needs to load code for every page before the current route can operate.

Before:

Initial application
├── Home
├── Login
├── other route code
└── dependencies
Enter fullscreen mode Exit fullscreen mode

After:

Initial route
├── shared dependencies
└── current page chunk

Additional page
└── loaded on demand
Enter fullscreen mode Exit fullscreen mode

This keeps the initial route focused on the code it actually needs.

4. Backend reliability improvement

The MongoDB issue was not treated as a performance metric because it was fundamentally a reliability problem.

The debugging process was:

MongoDB connection fails
        ↓
querySrv ECONNREFUSED
        ↓
DNS investigation
        ↓
Resolve-DnsName verification
        ↓
Direct connection workaround
        ↓
Backend connectivity restored
Enter fullscreen mode Exit fullscreen mode

This fix removed a development blocker that was preventing the API from connecting to the database.


Best Use of Sentry

I am submitting this project for the Best Use of Sentry category.

Sentry became part of the debugging workflow rather than simply being another dependency.

The workflow was:

Runtime problem
      ↓
Sentry captures the event
      ↓
Inspect issue + stack trace
      ↓
Locate affected code
      ↓
Fix
      ↓
Verify application behavior
Enter fullscreen mode Exit fullscreen mode

During development, Sentry captured frontend runtime errors including:

ReferenceError: VideoSkeleton is not defined
Enter fullscreen mode Exit fullscreen mode

Sentry provided:

  • issue details
  • event information
  • stack trace
  • browser/environment context
  • affected execution path

That helped me move from:

"Something is broken."

to:

"This specific code path is failing for this specific reason."

Sentry Evidence

Sentry became part of my debugging loop, not just a dependency in package.json. The workflow was consistent throughout the Bug Smash:

Runtime failure → Sentry captures event → inspect issue + stack trace + context → locate affected code → fix → verify.

One concrete example: during a refactor, I accidentally removed an import for VideoSkeleton. It didn't crash the build — it only threw at runtime when the loading state triggered. Without Sentry I'd have found it eventually when a user reported a blank page. With Sentry, I had it within minutes:

Sentry issue page showing the ReferenceError VideoSkeleton is not defined with its stack trace and debugging context

Sentry captured the ReferenceError with the full stack trace, release hash, browser and OS context, and event breadcrumbs. This turned "the page is broken sometimes" into a single actionable issue with a direct line to the failing code path.

What Sentry gave me that my terminal couldn't: the release hash tying the error to a specific build, the browser and OS the user hit it on, the URL and route where it happened, and the trace ID linking to surrounding events. That context is what turned a five-word error into a five-minute fix.

Importantly, I used Sentry for runtime observability and React Profiler for component rendering analysis — I didn't conflate them. Sentry told me what was failing; the Profiler told me how components were rendering. Keeping those two evidence streams separate gave me a cleaner picture of the application's actual behavior.


Best Use of Google AI

I am also submitting this project for the Best Use of Google AI category.

I used Gemini 3.1 Pro as a debugging partner during the Bug Smash rather than simply asking it to generate the implementation.

The clearest example was the MongoDB connection problem.

1. Problem

The backend returned:

querySrv ECONNREFUSED
Enter fullscreen mode Exit fullscreen mode

and the application could not establish its database connection.

2. Gemini investigation

I provided Gemini with the error and information about my Windows environment.

Gemini analyzed the failure and suggested investigating DNS and IPv4/IPv6 resolution behavior.

3. Independent verification

I did not blindly copy the recommendation.

I ran the diagnostic myself:

Resolve-DnsName <mongodb-host>
Enter fullscreen mode Exit fullscreen mode

and verified the DNS resolution problem.

4. Implementation

After confirming the issue, I changed the MongoDB connection strategy to use a direct connection configuration that avoided the failing SRV-resolution path.

5. Result

The backend was able to reconnect to MongoDB and resume normal operation.

The overall debugging loop became:

querySrv ECONNREFUSED
        ↓
Gemini analysis
        ↓
DNS hypothesis
        ↓
Resolve-DnsName verification
        ↓
Connection workaround
        ↓
Backend restored
Enter fullscreen mode Exit fullscreen mode

I treated Gemini's response as a hypothesis, not as the final answer. I independently verified the DNS behavior before changing the MongoDB connection strategy.

Gemini Evidence

Gemini 3.1 Pro conversation analyzing the MongoDB querySrv ECONNREFUSED DNS and IPv4 IPv6 issue

Use the screenshot showing Gemini analyzing the querySrv ECONNREFUSED problem and discussing the DNS/connection investigation.

Suggested caption: Gemini helped narrow down the MongoDB connection failure; I independently verified the suggested diagnostic path before implementing the workaround.

The important part for me was not simply generating code faster. It was shortening the path from an unfamiliar error to a testable hypothesis.


Final Impact

The clearest measured result was the reduction in network transfer:

2.0 MB → 471 KB

That's a 76.5% reduction.

The Load event changed from:

2.61 s → 1.60 s

which is a 38.7% reduction in Load time.

The total resource footprint changed from:

2.3 MB → 767 KB

which is a 66.7% reduction.

The React Profiler also showed that the optimized VideoCard could be skipped during the recorded search interaction when its props remained unchanged.

The MongoDB issue was resolved as a backend reliability fix, while Sentry and Gemini made the debugging workflow more observable and efficient.


What I Deliberately Did Not Claim

Not every metric improved.

Request count moved from 30 to 32, partly because a stale thumbnail returned 404. DOMContentLoaded moved from 593 ms to 624 ms, which I consider within the noise of this benchmark.

The strongest measured result is the reduction in transferred data from 2.0 MB to 471 KB, followed by the Load improvement from 2.61 s to 1.60 s.

The Sentry runtime event shown in this post was captured in development; validating the same workflow against production traffic is a next step.

Future Roadmap for Rawbee

The Bug Smash was focused on performance and reliability, but I also have a larger direction planned for Rawbee.

The next stage is to turn Rawbee from a traditional video-sharing application into a more interactive, social streaming platform.

Planned future work includes:

  • Live Rooms where users can create and join shared sessions
  • WebRTC video/audio calling
  • Screen sharing
  • Real-time room chat using Socket.io
  • Synchronized watch experiences
  • Multi-user collaborative streaming
  • Host controls and participant management

The goal is to let users watch and interact together without needing a separate Discord or video-call application.

These real-time features are future work and are not included in the performance measurements reported in this Bug Smash submission.


Conclusion

This Bug Smash started with a simple observation: Rawbee was getting slower as the application grew.

Instead of rewriting large parts of the project, I used measurement and debugging tools to identify where the actual problems were.

The result was a focused set of improvements:

React.memo
      ↓
Less unnecessary rendering

Cloudinary transformations
      ↓
Smaller image payloads

React.lazy + Suspense
      ↓
Route-level code splitting

DNS investigation + MongoDB workaround
      ↓
Backend reliability restored

Sentry
      ↓
Runtime visibility

Gemini
      ↓
Faster debugging investigation
Enter fullscreen mode Exit fullscreen mode

The biggest measurable performance improvement was:

2.0 MB → 471 KB transferred

with the production Load event improving from:

2.61 s → 1.60 s

The main takeaway is simple:

Before optimizing code, measure what the application is actually doing.

That is what I tried to do throughout this Bug Smash: identify the real bottlenecks, fix them deliberately, and verify the results instead of relying on assumptions.

Implementation

GitHub Pull Request: View PR #1

Repository: View Rawbee on GitHub

Thanks for checking out my submission!

Top comments (0)