I Thought Rawbee Was Slow — Then I Opened the Network Tab
This is a submission for DEV's Summer Bug Smash: Smash Stories powered by Sentry.
The Project
Rawbee is a full-stack video-sharing and real-time streaming platform that I built as a YouTube-inspired application.
It currently includes:
- Video uploads
- Video discovery
- Subscriptions
- Watch history
- Live streaming
- WebRTC-based functionality
- Screen sharing
- Socket.IO communication
- Cloudinary media delivery
The stack includes:
- Frontend: React 18, TypeScript, Tailwind CSS, Vite
- Backend: Node.js, Express
- Database: MongoDB
- Media: Cloudinary
- Real-time: Socket.IO, WebRTC
- Observability: Sentry
- AI debugging: Google Gemini
Repository: View Rawbee on GitHub
Bug Smash PR: View the Pull Request
For this Bug Smash, I deliberately stopped adding new features and focused on one question:
Where is Rawbee doing unnecessary work, and can I prove that my fixes actually made it better?
The Moment I Realized Something Was Wrong
Nothing was obviously broken.
The homepage loaded. Videos appeared. Search worked.
But the application felt heavier than it should.
My first instinct was to blame React.
When I typed into the search bar, the parent component updated and the video-card tree was being evaluated again.
So I opened React DevTools and started profiling.
The profiler showed repeated VideoCard renders during a simple interaction, even when the underlying video data had not changed.
That was the first bug.
Then I opened the Network tab.
And I found a much bigger problem.
The Network Tab Changed Everything
The production homepage was transferring approximately:
2.0 MB
Some individual image assets were enormous for their displayed size:
834 KB
433 KB
160 KB
An 834 KB thumbnail for a relatively small video card immediately looked wrong.
At that point, the problem stopped being:
"The page feels slow."
and became:
"The browser is downloading far more data than this UI should need."
Before: Production Baseline
📸 SCREENSHOT 1 — BEFORE NETWORK
Production baseline before optimization. The homepage transferred approximately 2.0 MB, with individual image assets reaching 834 KB, 433 KB, and 160 KB.
The baseline showed:
- 30 requests
- 2.0 MB transferred
- 2.3 MB resources
- 593 ms DOMContentLoaded
- 2.61 s Load
- 2.82 s Finish
For the first time, I had a real baseline instead of a vague feeling that the application was slow.
Fix #1 — Stopping Unnecessary React Renders
The first fix was React.memo().
The problem looked roughly like this:
Search state changes
↓
Home re-renders
↓
VideoCard renders again
But if the video's data hasn't changed, that child component doesn't necessarily need to render again.
I changed the component to:
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;
I also moved pure formatting helpers outside the component so their definitions weren't recreated during every component execution.
The important optimization, however, was React.memo() combined with stable props.
When the video prop remains referentially equal, React can skip rendering the component.
React Profiler — Before
📸 SCREENSHOT 2 — REACT PROFILER BEFORE
Before memoization: VideoCard renders repeatedly during the recorded interaction as parent state changes.
The profiler gave me a concrete reason to make the optimization instead of blindly applying React.memo().
React Profiler — After
📸 SCREENSHOT 3 — REACT PROFILER AFTER
After memoization: the Profiler explicitly reports "Did not render on the client during this profiling session." The memoized component is skipped when its props remain unchanged.
The important result wasn't the raw number of React commits.
It was seeing React actually skip the component when its props remained unchanged.
Fix #2 — The 834 KB Thumbnail
The React optimization removed unnecessary rendering work.
But the Network tab was still pointing at the much larger problem:
image delivery.
The original application requested full-size images from Cloudinary even when the UI only needed a relatively small thumbnail.
I created a helper:
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 transformation uses:
-
w_500to resize the image -
f_autoto let Cloudinary select an appropriate format -
q_autoto optimize quality
Before optimization, some image responses were approximately:
834 KB
433 KB
160 KB
After optimization, examples included:
7.3 KB
2.0 KB
22.0 KB
2.6 KB
This became the biggest contributor to the overall network improvement.
Instead of:
2.0 MB
the optimized production build transferred:
471 KB
That's a:
76.5% reduction in transferred data.
After: Optimized Production Build
📸 SCREENSHOT 4 — AFTER NETWORK
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.
Fix #3 — Why Load JavaScript for Pages the User Hasn't Opened?
Rawbee contains multiple routes.
But a user visiting the homepage doesn't need every page's JavaScript immediately.
So I introduced route-level code splitting using React.lazy() and Suspense:
import { lazy, Suspense } from "react";
const Home = lazy(
() => import("./pages/Home")
);
const Login = lazy(
() => import("./pages/Login")
);
function App() {
return (
<Router>
<Suspense <div className="flex h-screen items-center justify-center" fallback="{">
Loading...
</div>
}
>
<Routes>
<Route element="{<Login" path="/login"/>}
/>
<Route element="{<Home" path="/"/>}
/>
</Routes>
</Suspense>
</Router>
);
}
Instead of:
Application
↓
Everything loads immediately
the browser can now load:
Current route
↓
Current route chunk
↓
User navigates
↓
Additional route chunk
This keeps the initial JavaScript workload focused on the route the user is actually visiting.
Fix #4 — Cleaning Up Sentry Configuration
While working on observability, I also found that the Sentry DSN was hardcoded in the source.
I moved it to environment configuration.
Before:
Sentry.init({
dsn: "your_dsn_here"
});
After:
Sentry.init({
dsn: import.meta.env.VITE_SENTRY_DSN
});
with:
VITE_SENTRY_DSN=your_dsn_here
This makes the configuration easier to manage across environments and keeps environment-specific configuration outside application source.
Fix #5 — When MongoDB Stopped Connecting
While working on the frontend, I encountered a completely different problem.
MongoDB stopped connecting and the backend reported:
querySrv ECONNREFUSED
At first, I treated it as a MongoDB problem.
But the failure was occurring during DNS/SRV resolution.
I investigated the DNS behavior using:
Resolve-DnsName <mongodb-host>
After confirming the DNS resolution problem in my Windows environment, I changed the MongoDB connection strategy to a direct connection configuration as a workaround.
The backend then reconnected successfully.
Best Use of Sentry
Sentry became part of my debugging workflow rather than something I added only after the application was finished.
My process became:
Runtime problem
↓
Sentry captures the event
↓
Inspect issue + stack trace + context
↓
Locate affected code
↓
Fix
↓
Verify
One concrete example happened during a refactor.
I accidentally removed an import for VideoSkeleton.
The application could still build successfully.
But when the loading path executed at runtime, the browser threw:
ReferenceError: VideoSkeleton is not defined
Sentry captured the runtime failure and provided the stack trace and surrounding debugging context.
Instead of:
"Something is broken."
I now had:
"This exact runtime path is throwing this exact error."
📸 SCREENSHOT 5 — SENTRY ISSUE
Sentry captured the runtime failure with the stack trace and execution context used during debugging.
Sentry and React Profiler Answered Different Questions
One of the biggest lessons from the project was that different debugging tools answer different questions.
Sentry
What is failing?
React Profiler
What is rendering?
Chrome Network
What is the browser downloading?
Using separate tools for separate questions made the investigation much clearer.
Best Use of Google AI
I used Google Gemini 3.1 Pro as a debugging partner rather than simply asking it to generate code.
The MongoDB DNS error was the clearest example.
I provided Gemini with:
- The
querySrv ECONNREFUSEDerror - My Windows environment
- Node.js information
- The MongoDB connection behavior
Gemini helped investigate possible causes involving DNS resolution, MongoDB SRV records, and IPv4/IPv6 behavior.
One useful diagnostic was:
Resolve-DnsName _mongodb._tcp.cluster0.x3qwygg.mongodb.net
I ran the command myself.
That distinction was important:
I treated Gemini's response as a hypothesis, not as proof.
I independently verified the behavior before changing the connection configuration.
The debugging loop became:
MongoDB querySrv ECONNREFUSED
↓
Gemini analysis
↓
DNS / IPv4-IPv6 hypothesis
↓
Resolve-DnsName verification
↓
Connection configuration workaround
↓
Backend restored
📸 SCREENSHOT 6 — GEMINI DEBUGGING
Gemini helped narrow down the MongoDB connection failure; I independently verified the suggested diagnostic path before implementing the workaround.
The useful part of AI debugging wasn't:
"AI wrote code for me."
It was:
"AI helped me turn an unfamiliar error into a testable hypothesis."
The Final Benchmark
After applying the fixes, I repeated the production benchmark under the same conditions:
- Chrome
- Fast 4G throttling
- Browser cache disabled
- Same homepage
- Same machine
- Same production preview workflow
| Metric | Before | After | Change |
|---|---|---|---|
| Network transferred | 2.0 MB | 471 KB | −76.5% |
| Resources loaded | 2.3 MB | 767 KB | −66.7% |
| Load event | 2.61 s | 1.60 s | −38.7% |
| Finish | 2.82 s | 1.81 s | −35.8% |
| Requests | 30 | 32 | +2 |
| DOMContentLoaded | 593 ms | 624 ms | +31 ms |
The two numbers I cared about most were:
2.0 MB → 471 KB
76.5% less network data transferred
and:
2.61 s → 1.60 s
38.7% faster Load event
What Didn't Improve
Not every metric improved.
The request count changed:
30 → 32
and DOMContentLoaded changed:
593 ms → 624 ms
I also had a stale thumbnail returning a 404, which I flagged as follow-up work.
I don't want to turn a benchmark into:
"Everything got better."
It didn't.
The meaningful result was the large reduction in transferred data and the improvement in the Load event.
Those are the results I can actually demonstrate.
What I'm Most Proud Of
The thing I'm most proud of isn't a particular line of code.
It's the change in how I approached the problem.
Before this work, my instinct would have been:
"The application feels slow. I should optimize the code."
Now the process is:
Observe
↓
Measure
↓
Hypothesize
↓
Test
↓
Fix
↓
Measure again
I thought React rendering was the main problem.
Then the Network tab showed me that an 834 KB thumbnail was a much bigger issue.
That was a useful reminder:
The thing that feels slow isn't always the thing that is actually expensive.
What I Learned
Profile before optimizing
React.memo() is useful when you can identify unnecessary renders.
The profiler gave me evidence to make the change.
Network payload matters
A frontend can have clean React code and still feel slow if the browser downloads unnecessary data.
Observability changes debugging
Sentry turned runtime errors into actionable issues instead of vague UI failures.
AI works best with verification
Gemini helped me investigate the MongoDB problem faster, but I still tested the hypothesis myself.
Not every metric needs to improve
A useful benchmark should report the actual result, including the numbers that didn't move in the desired direction.
What's Next for Rawbee?
The Bug Smash focused on performance, reliability, and debugging.
But Rawbee is still evolving.
The next direction is a more interactive social streaming experience.
Future work includes:
- Live Rooms
- WebRTC video/audio
- Screen sharing
- Real-time Socket.IO chat
- Synchronized watch parties
- Multi-user co-streaming
- Host controls
The goal is eventually to let users watch, communicate, and participate together without needing a separate application for every part of the experience.
These are future roadmap features and are not included in the performance measurements above.
Final Result
The most important transformation was:
2.0 MB → 471 KB
A 76.5% reduction in network transfer.
The Load event changed from:
2.61 s → 1.60 s
A 38.7% improvement.
Along the way I also:
- Reduced unnecessary
VideoCardrendering - Optimized Cloudinary image delivery
- Added route-level code splitting
- Improved Sentry configuration
- Diagnosed a MongoDB DNS/SRV failure
- Used Sentry for runtime observability
- Used Gemini to accelerate debugging
No rewrite.
No new framework.
Just:
Measure → Understand → Fix → Verify
Links
Rawbee Repository:
https://github.com/Pritam-mb/rawbee
Bug Smash Pull Request:
https://github.com/Pritam-mb/rawbee/pull/1
Thanks for reading.
And thanks to DEV and Sentry for giving me a reason to stop adding features for a moment and ask:
How well does the application I've already built actually work?






Top comments (0)