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
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
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
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
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
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;
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/`
);
};
Then:
<img
src={getOptimizedCloudinaryUrl(video.thumbnail, 500)}
alt={video.title}
/>
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>
);
}
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
// frontend/src/main.tsx
Sentry.init({
- dsn: "your_dsn_here",
+ dsn: import.meta.env.VITE_SENTRY_DSN,
});
This makes the configuration easier to manage across different environments.
5. MongoDB DNS Failure
The backend was failing with:
querySrv ECONNREFUSED
I investigated the DNS behavior in the Windows environment and used:
Resolve-DnsName <mongodb-host>
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
After optimization:
Search state changes
↓
Home updates
↓
React.memo checks props
↓
VideoCard render skipped when props are unchanged
React Profiler Evidence
React Profiler evidence — before:
Before memoization: VideoCard renders repeatedly across the profiling session as parent state updates.
React Profiler evidence — after:
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
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
This change was the main contributor to:
2.0 MB → 471 KB
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
After:
Initial route
├── shared dependencies
└── current page chunk
Additional page
└── loaded on demand
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
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
During development, Sentry captured frontend runtime errors including:
ReferenceError: VideoSkeleton is not defined
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 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
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>
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
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
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
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)