{
  "source_file": "0930",
  "source_platform": "Hashnode",
  "author": "Jaival Suthar",
  "title_listed": "How I Reduced Our React Bundle by 62%: Lessons in Real-World Performance Engineering",
  "keywords": "",
  "domain": "机器学习",
  "content_type": "深度技术解析",
  "is_paid_flag": "否",
  "url": "https://jaivalsuthar.hashnode.dev/how-i-reduced-our-react-bundle-by-62-lessons-in-real-world-performance-engineering",
  "publish_date": "2025-10-23",
  "corpus_stage": "",
  "notes": "",
  "id": "268ff4bc20fb",
  "collected_at": "2026-10-02T14:12:17",
  "title": "React Bundle Optimization Case Study: Reducing Bundle Size with Vite",
  "body_markdown": "How I Reduced Our React Bundle by 62%: Lessons in Real-World Performance Engineering\nUpdated\nFebruary 1, 2026\n•\n13\nmin read\n•\nView as Markdown\nJ\nJaival Suthar\nI write technical articles that analyze real world engineering problems through the lens of production systems.\nMy work focuses on architectural tradeoffs, UI layer system design, performance optimization, and reliability patterns derived from shipping and maintaining scalable applications.\nTL;DR: What You’ll Learn\nHow to visualize bundle bloat with rollup-plugin-visualizer\nWhy switching PrimeIcons → React Icons saved 550KB instantly\nThe lazy loading mistake that made things 90% worse (yes, worse)\nRoute-level vs component-level code splitting: where to draw the line\nFinal result: 62% reduction, zero warnings, and hard-earned lessons\n⚠️ Learning Journey Disclaimer\nThis article documents a\nlocal optimization work\nI conducted as an intern. All metrics are from my development environment, not production deployments. While the techniques are production-ready, the specific performance improvements haven’t been validated with real users yet. This is my learning process, shared transparently, including the spectacular Build 3 failure.\nThis was less about shrinking numbers and more about learning how to reason about performance trade-offs in real systems.\nThe Problem: When Your Production Build Screams for Help\nYou’ve just wrapped up a development sprint. Everything works perfectly in development. You run\nnpm run build\nto preview the production bundle, and your terminal erupts in warnings:\n⚠️ Some chunks are larger than 500 kB after minification.\n- Consider using dynamic import() to code-split the application\n- Use build.rollupOptions.output.manualChunks to improve chunking\nThat was my reality as an engineering intern curious about production builds. While working on a React-based internal tool, I ran\nnpm run build\none afternoon and discovered that my main application chunk had ballooned to\n893.07 kB\nwith a single CSS warning still lingering. The total bundle sat at 1,976.3 kB uncompressed and 409.65 kB when compressed.\nInitially, I wondered if I should just ignore these warnings. After all, the app worked fine locally. But after researching the real-world impact of bundle size on Core Web Vitals, user retention, and mobile performance, I realized this was an opportunity to dive deep into performance optimization.\nThis wasn’t just a warning to dismiss. It was a critical performance liability. Every unnecessary kilobyte translates directly to:\nLonger Time to Interactive (TTI)\nHigher bounce rates on slower networks\nIncreased CDN and hosting costs\nPoor Core Web Vitals scores damaging SEO\nFrustrated users, especially on mobile devices\nWhat followed was a systematic optimization journey spanning four builds, including one spectacular disaster that taught me more than any success. Here’s the complete story.\nAbout This Project\nThis optimization was performed\nlocally on my development machine\nas a learning exercise. I’m working on a React-based internal tool built with Vite, and after noticing build warnings, I decided to systematically investigate bundle optimization strategies.\nImportant context:\nThis hasn’t been deployed to production yet. All metrics and results in this article come from local builds and my development environment. I’m documenting this process both as a learning exercise.\nStep 1: Profiling the Bundle with rollup-plugin-visualizer\nBefore making any changes, I needed complete visibility into what was consuming bundle space. You can’t optimize what you can’t measure.\nWhy rollup-plugin-visualizer?\nVite uses Rollup under the hood, and\nrollup-plugin-visualizer\ngenerates an interactive treemap visualization of your production bundle. Think of it as Chrome DevTools' Coverage tab, but specifically for your build output, showing exactly which packages and modules are consuming space.\nInstallation:\nnpm install --save-dev rollup-plugin-visualizer\nConfiguration in\nvite.config.js\n:\nimport { visualizer } from 'rollup-plugin-visualizer';\nexport default defineConfig({\n  plugins: [\n    react(),\n    visualizer({\n      open: true,\n      gzipSize: true,\n      brotliSize: true,\n      filename: './dist/stats.html'\n    })\n  ]\n});\nRunning the production build now generates an interactive HTML report showing:\nParsed size (actual file size on disk)\nCompressed size (what actually gets transferred)\nBrotli size (even better compression for modern CDNs)\nVisual treemap of which packages consume the most space\nWhat the Visualizer Exposed\nThe treemap immediately revealed the problems:\nPrimeIcons\n: Shipping an entire CSS-based icon font library despite using only ~15 icons\nHeavy components\n: Large third-party UI libraries loaded synchronously on initial render\nThe visualizer became my north star for every subsequent optimization. Each change would be validated by re-running the build and checking the treemap.\nStep 2:\nSurgical Dependency Replacement: PrimeIcons to React Icons\nBuild 1 Baseline\nMain chunk: 893.07 kB\nTotal bundle: 1,976.3 kB\nTotal compressed: 409.65 kB\nJS Warnings: 1\nCSS Warnings: 1\nBuild Time: 8.76s\nThe PrimeIcons Problem\nPrimeIcons follows the traditional icon font approach, a monolithic architecture that is fundamentally incompatible with modern tree-shaking:\nShips a complete CSS file containing\nall\nicon definitions\nLoads every icon’s vector data via font files, regardless of actual usage\nRequires className-based styling:\n<i className=\"pi pi-home\">\nZero tree-shaking\n: you pay the full cost even if you use a single icon\nIn my case, I was using approximately 15 icons but shipping the entire library to every user.\nWhy React Icons?\nReact Icons is a component-based, tree-shakeable icon library that aggregates 20+ popular icon sets (Font Awesome, Material Icons, Feather, Heroicons, Bootstrap Icons, and more) into a unified API.\nKey Technical Advantages:\n1. Aggressive Tree-Shaking Through ES Modules\nReact Icons uses ES module exports with each icon as a standalone React component. Modern bundlers like Vite can analyze the import graph and eliminate unused code at build time.\n// Before: PrimeIcons (loads entire library)\nimport 'primeicons/primeicons.css';\n<i className=\"pi pi-home\" />\n// After: React Icons (only bundles what you import)\nimport { FaHome } from 'react-icons/fa';\n<FaHome />\nOnly\nFaHome\ngets bundled. The other 2,000+ icons? Completely eliminated. This is dead code elimination at its finest.\n2. Type Safety and Developer Experience\nIcons become first-class React components with full TypeScript definitions:\nIntelliSense autocomplete in your IDE\nCompile-time error checking\nNo more typos in className strings\nClear dependency tracking\n3. Props-Based Styling\n<FaHome \n  size={24} \n  color=\"#3b82f6\" \n  style={{ marginRight: 8 }}\n  className=\"hover:scale-110 transition-transform\"\n/>\nSize, color, and styles are props, with no CSS classes required. This makes dynamic theming trivial and eliminates global CSS pollution.\n4. Icon Set Flexibility\nNeed icons from multiple design systems? React Icons makes it seamless:\nimport { MdDashboard } from 'react-icons/md';  // Material Design\nimport { FaUserCircle } from 'react-icons/fa';  // Font Awesome\nimport { HiChartBar } from 'react-icons/hi';    // Heroicons\nimport { BiCog } from 'react-icons/bi';         // Bootstrap Icons\nThis design-system agnosticism is invaluable as product requirements evolve. No need to commit to a single icon family upfront.\n5. Performance at Scale\nPure React components with minimal runtime overhead\nBetter integration with React’s reconciliation algorithm\nNo global CSS to parse\nEasier to audit dependencies\nSimpler to update without breaking changes\nBusiness Impact Beyond Bundle Size\nDeveloper Velocity\n: Switching icons no longer requires hunting through CSS documentation. Import, use, done.\nDesign Flexibility\n: Product designers can mix icon styles without engineering constraints.\nMaintainability\n: Explicit imports make it trivial to find and update icon usage. No more grep-ing for className strings.\nFuture-Proofing\n: React Icons tracks upstream icon libraries, ensuring access to the latest icons without manual CSS updates.\nMigration Strategy\nI approached the migration systematically:\nAudit current usage\n: Ran global search for\nclassName=\"pi\nto identify all PrimeIcons instances\nCreate mapping document\n: Built a table mapping PrimeIcons to React Icons equivalents (e.g.,\npi-home\n→\nFaHome\n)\nIncremental replacement\n: Swapped icons file-by-file, testing for visual regressions\nRemove PrimeIcons\n: Uninstalled the package and purged all CSS imports\nValidate\n: Checked treemap to confirm elimination\nBuild 2 Results\nMain chunk: 893.07 kB (no change)\nTotal bundle: 1,425.92 kB (-550.38 kB, -28%)\nTotal compressed: 304.6 kB (-105.05 kB, -26%)\nJS Warnings: 0 ✅\nCSS Warnings: 1\nBuild Time: 6.81s (-22%)\nCritical Observation\n: The main chunk size didn’t decrease, but the\ntotal bundle dropped by 550 kB\nand\nbuild time improved by 22%\n.\nWhy? PrimeIcons’ CSS and font files were distributed across multiple asset files. Eliminating them had a cascading effect on the overall bundle. Additionally, React Icons’ compile-time approach reduced the work Vite needed to do during builds.\nThe JS warning disappeared completely, a major win for build health.\nStep 3: The Lazy Loading Disaster (And Recovery)\nEncouraged by the PrimeIcons success, I decided to implement aggressive code-splitting using React’s\nlazy()\nand\nSuspense\n. The theory was sound: split the application into smaller chunks loaded on-demand, reducing initial bundle size.\nThe Mistake: Over-Aggressive Component Splitting\nI lazily loaded almost everything:\n// Pages (reasonable)\nconst Dashboard = lazy(() => import('./pages/Dashboard'));\nconst Analytics = lazy(() => import('./pages/Analytics'));\nconst Settings = lazy(() => import('./pages/Settings'));\n// Components (mistake)\nconst Navbar = lazy(() => import('./components/Navbar'));\nconst Sidebar = lazy(() => import('./components/Sidebar'));\nconst Footer = lazy(() => import('./components/Footer'));\nconst UserProfile = lazy(() => import('./components/UserProfile'));\nconst DataTable = lazy(() => import('./components/DataTable'));\n// ...and many more\nI even split small utility components that were used across multiple pages. The logic seemed reasonable: “smaller chunks = better performance, right?”\nBuild 3 Results (The Catastrophe)\nMain chunk: 1,291.70 kB (+398.63 kB, +45%) 🔴\nTotal bundle: 2,706.42 kB (+1,280.5 kB, +90%) 🔴\nTotal compressed: 740.79 kB (+436.19 kB, +143%) 🔴\nJS Warnings: 1 ⚠️\nBuild Time: 8.76s (+29%)\nCode Splitting: MAJOR REGRESSION\nThe bundle\nexploded\n. Not only was the main chunk 45% larger, but the total bundle nearly doubled. The compressed size grew by 143%, which was even worse than the uncompressed growth. Build warnings returned.\nRoot Cause Analysis: Why Over-Splitting Failed\n1. Shared Dependency Duplication\nWhen you aggressively split components, Rollup attempts to extract shared dependencies into common chunks. However, its heuristics aren’t perfect. I ended up with:\nReact duplicated across 8+ chunks\nReact Router code in 12 chunks\nUtility functions copy-pasted everywhere\nThe bundler couldn’t determine optimal common chunks, so it erred on the side of duplication to avoid complex dependency graphs.\n2. HTTP Request Overhead\nInstead of one optimized 337 kB chunk, I had:\n47 chunks ranging from 5 KB to 200 KB\nMultiple waterfall requests as components loaded other lazy components\nConnection setup overhead for each request\n3. Waterfall Loading Cascade\nThe real killer was sequential loading:\nUser visits page → Loads main chunk → Parses\n→ Encounters lazy Navbar → Loads Navbar chunk → Parses\n→ Navbar lazy loads UserProfile → Loads UserProfile chunk → Parses\n→ UserProfile lazy loads Avatar → Loads Avatar chunk → Parses\nThis would create a 4-level waterfall in production where each component had to fully load before the next could start.\n4. Compression Efficiency Loss\nGzip and Brotli build compression dictionaries based on repeated patterns in files. Larger files provide more opportunities for dictionary building, achieving better compression ratios.\nBy splitting into tiny chunks, I eliminated these opportunities:\nA 100 KB file might compress to 25 KB (75% compression)\nTen 10 KB files might compress to 4 KB each (60% compression each)\nNet result: 40 KB vs 25 KB, 60% more data transferred\nThis explained why my compressed size grew even more dramatically than raw size.\n5. Module Boundary Overhead\nEach lazy chunk includes Vite/Webpack runtime code for:\nDynamic import handling\nModule registry updates\nChunk dependency resolution\nError boundary logic\nWith 47 chunks, this overhead appeared 47 times. In a monolithic bundle, it appears once.\nThe Recovery: Strategic, Route-Level Code Splitting\nI reverted all component-level splitting and applied lazy loading\nonly\nwhere it provided measurable value:\n✅ Route-Level Code Splitting (The Sweet Spot)\n// Excellent: Split by routes\nconst Dashboard = lazy(() => import('./pages/Dashboard'));\nconst Analytics = lazy(() => import('./pages/Analytics'));\nconst AdminPanel = lazy(() => import('./pages/AdminPanel'));\nconst Reports = lazy(() => import('./pages/Reports'));\n<Suspense fallback={<PageLoader />}>\n  <Routes>\n    <Route path=\"/dashboard\" element={<Dashboard />} />\n    <Route path=\"/analytics\" element={<Analytics />} />\n    <Route path=\"/admin\" element={<AdminPanel />} />\n    <Route path=\"/reports\" element={<Reports />} />\n  </Routes>\n</Suspense>\nWhy this works perfectly:\nUsers navigate to different routes at different times\nRoute boundaries are natural application seams\nEach route chunk is substantial (50–150 kB), enabling good compression\nNo waterfall loading. Route components are top-level.\nUsers get only what they need, when they need it\n✅ Heavy, Conditional Features\n// Good: Lazy load large, rarely-used features\nconst ChartingDashboard = lazy(() => import('./features/ChartingDashboard'));\nconst PDFGenerator = lazy(() => import('./features/PDFGenerator'));\nconst VideoPlayer = lazy(() => import('./features/VideoPlayer'));\n// Only load when user explicitly requests\n{showAdvancedCharts && (\n  <Suspense fallback={<Spinner />}>\n    <ChartingDashboard />\n  </Suspense>\n)}\nThese components are:\nLarge (100+ KB)\nUsed by <20% of users\nBehind user actions (clicks, toggles)\nSelf-contained with minimal dependencies\n❌ Critical UI Components (Never Split These)\n// Bad: Don't lazy load critical path components\nimport Navbar from './components/Navbar';\nimport Sidebar from './components/Sidebar';\nimport Footer from './components/Footer';\nimport ErrorBoundary from './components/ErrorBoundary';\nWhy synchronous imports are mandatory here:\nThese components render on\nevery\npage load\nUsers see them immediately, since splitting only delays FCP.\nThey’re part of the application skeleton\nNo caching benefit since they’re always needed\nSplitting creates layout shift and loading flashes\nThe principle I learned: If a component appears on initial render of any route, it belongs in the main bundle.\nBuild 4 Results (The Victory)\nMain chunk: 336.87 kB (-954.83 kB from Build 3, -62% from Build 1)\nTotal bundle: 1,427.13 kB (-1,279.29 kB, nearly back to Build 2)\nTotal compressed: 292.29 kB (-448.5 kB, -29% from Build 1)\nJS Warnings: 0 ✅\nCSS Warnings: 0 ✅\nBuild Time: 7.43s\nCode Splitting: Excellent ✅\nLargest Chunk: 145.93 kB ✅\nBy limiting lazy loading to routes and conditional features, I achieved:\n62% main chunk reduction\nfrom baseline\n28% total bundle reduction\nfrom baseline\n29% smaller compressed size\nfrom baseline\nAll warnings eliminated\nProper code-splitting without overhead\nExcellent compression ratios restored\nBuild Progression: Complete Analysis\nKey Learnings: What Worked and What Catastrophically Failed\n✅ Strategies That Delivered Results\nBundle Analysis First\n: rollup-plugin-visualizer prevented blind optimization\nTree-Shakeable Dependencies\n: React Icons, date-fns, modular Lodash eliminated dead code\nRoute-Level Code Splitting\n: Perfect granularity for lazy loading\nManual Vendor Chunks\n: Improved cache hit rates and prevented duplication\nMeasuring Compressed Sizes\n: Gzip metrics matter more than raw file sizes\n❌ Strategies That Backfired Spectacularly\nComponent-Level Lazy Loading\n: Created exponentially more problems than solutions\nSplitting Critical UI\n: Navbar/Sidebar splitting delayed FCP with zero benefit\nIgnoring Compression Efficiency\n: Small chunks compress poorly. This is a critical oversight.\nPremature Optimization\n: Applying lazy loading before profiling wasted days\nChasing Warnings Blindly\n: Build 3 fixed warnings but destroyed performance\nProduction Best Practices for React Bundles\n1. Establish Bundle Budgets in CI/CD\n// package.json\n{\n  \"scripts\": {\n    \"build\": \"vite build\",\n    \"build:check\": \"vite build && node scripts/check-bundle-size.js\"\n  }\n}\nFail builds if bundles exceed thresholds. Prevent regressions before they ship.\n2. Monitor Continuously with Lighthouse CI\nIntegrate Lighthouse into CI to catch performance regressions:\n# .github/workflows/lighthouse.yml\n- name: Run Lighthouse CI\n  uses: treosh/lighthouse-ci-action@v9\n  with:\n    budgetPath: ./budget.json\n    uploadArtifacts: true\n3. Educate Your Team\nShare bundle reports in code reviews. Include screenshots from rollup-plugin-visualizer in PRs that add dependencies. Make performance everyone’s responsibility.\n4. Implement Progressive Enhancement\nCore functionality should work even if lazy chunks fail to load. Use error boundaries:\n<Suspense fallback={<Spinner />}>\n  <ErrorBoundary fallback={<ErrorMessage />}>\n    <LazyComponent />\n  </ErrorBoundary>\n</Suspense>\n5. Audit Dependencies Quarterly\nSet calendar reminders to review\nnode_modules\n. Libraries grow over time. That 50 KB package might be 150 KB now.\nKey Takeaways for Your Next Performance Optimization\nAlways profile first\n: You can’t optimize what you can’t measure\nStart with low-hanging fruit\n: Dependencies are often the biggest wins\nUnderstand compression\n: Small chunks compress poorly; size your chunks accordingly\nRespect natural boundaries\n: Routes are perfect split points; small components aren’t\nTest incrementally\n: One change at a time, with full metrics each step\nEmbrace failure as learning\n: Build 3 taught me more than any tutorial\nThink holistically\n: Bundle size is just one metric; consider TTI, FCP, and user experience\nConclusion\nAs an intern, tackling this optimization taught me more about React, bundling, and production engineering than any tutorial could. The Build 3 disaster was humbling but invaluable, as it taught me that understanding why an optimization works matters more than blindly applying best practices from blog posts.\nThe journey from 893KB to 337KB was not just about bundle size. It was about developing engineering judgment, including knowing when to optimize, how to measure impact, and when to roll back and try a different approach.\nBundle optimization is a discipline, not a destination. This journey reinforced that more optimization is not always better. It is about the right optimization applied at the right boundaries, informed by real data and user needs.\nIf you’re seeing build warnings in your React projects, treat it as an opportunity to learn. The tools exist, the techniques work, and yes, you’ll make mistakes along the way (I certainly did). But each mistake teaches you something valuable.\nIf deployed, these optimizations could benefit users, reduce infrastructure costs, and improve team velocity. I’m documenting my process openly, including failures, to help other junior developers approach performance optimization\nsystematically.\nResources\nrollup-plugin-visualizer\n: Bundle analysis treemap\nReact Icons\n: Tree-shakeable icon components\nVite Build Optimizations\n: Official Vite performance guide\nWeb.dev Performance Budgets\n: Google’s bundle size recommendations\nNote:\nThis article was originally published on Medium and is republished here for wider access and long-term reference.\n#\nreactjs\n#\njavascript\n#\nperformance\n#\nvite\n#\nsoftware-engineering\n#\nfrontend-development",
  "metadata": {
    "title": "React Bundle Optimization Case Study: Reducing Bundle Size with Vite",
    "description": "A practical case study on reducing React bundle size using Vite, Rollup visualizer, and strategic code splitting, including lessons from failed optimization",
    "keywords": null,
    "author": null,
    "og:title": "React Bundle Optimization Case Study: Reducing Bundle Size with Vite",
    "og:description": "A practical case study on reducing React bundle size using Vite, Rollup visualizer, and strategic code splitting, including lessons from failed optimization",
    "og:url": "https://medium.com/@jaivalsuthar/how-i-reduced-our-react-bundle-by-62-a-junior-developers-optimization-journey-e0f5a2ca6ee6",
    "og:site_name": "Jaival Suthar | Engineering Portfolio",
    "og:image": "https://cdn.hashnode.com/res/hashnode/image/upload/v1769966793110/54f69233-4b29-4d0e-825f-837fd7caa487.png",
    "og:image:width": "1200",
    "og:image:height": "630",
    "og:type": "article",
    "twitter:card": "summary_large_image",
    "twitter:creator": "https://x.com/Jaival_Suthar",
    "twitter:title": "React Bundle Optimization Case Study: Reducing Bundle Size with Vite",
    "twitter:description": "A practical case study on reducing React bundle size using Vite, Rollup visualizer, and strategic code splitting, including lessons from failed optimization",
    "twitter:image": "https://cdn.hashnode.com/res/hashnode/image/upload/v1769966793110/54f69233-4b29-4d0e-825f-837fd7caa487.png",
    "article:published_time": "2025-10-23T18:30:00.000Z",
    "article:modified_time": "2026-02-01T17:26:53.496Z",
    "article:author": "https://jaivalsuthar.hashnode.dev/@JaivalSuthar"
  },
  "paywall": {
    "listed_paid": false,
    "detected": false,
    "marker": "",
    "public_only": false,
    "policy": "付费/会员内容只保留页面公开可见部分，不绕过付费墙"
  },
  "crawl": {
    "success": true,
    "status_code": 200,
    "error": ""
  },
  "code_blocks": [
    {
      "file": "attachments/268ff4bc20fb/code/body-000.txt",
      "chars": 188,
      "language": "text"
    },
    {
      "file": "attachments/268ff4bc20fb/code/body-001.txt",
      "chars": 48,
      "language": "text"
    },
    {
      "file": "attachments/268ff4bc20fb/code/body-002.txt",
      "chars": 55,
      "language": "text"
    },
    {
      "file": "attachments/268ff4bc20fb/code/body-003.txt",
      "chars": 188,
      "language": "text"
    },
    {
      "file": "attachments/268ff4bc20fb/code/body-004.txt",
      "chars": 124,
      "language": "text"
    },
    {
      "file": "attachments/268ff4bc20fb/code/body-005.txt",
      "chars": 110,
      "language": "text"
    },
    {
      "file": "attachments/268ff4bc20fb/code/body-006.txt",
      "chars": 105,
      "language": "text"
    },
    {
      "file": "attachments/268ff4bc20fb/code/body-007.txt",
      "chars": 124,
      "language": "text"
    },
    {
      "file": "attachments/268ff4bc20fb/code/body-008.txt",
      "chars": 258,
      "language": "text"
    },
    {
      "file": "attachments/268ff4bc20fb/code/body-009.txt",
      "chars": 183,
      "language": "text"
    },
    {
      "file": "attachments/268ff4bc20fb/code/body-010.txt",
      "chars": 197,
      "language": "text"
    },
    {
      "file": "attachments/268ff4bc20fb/code/body-011.txt",
      "chars": 352,
      "language": "text"
    },
    {
      "file": "attachments/268ff4bc20fb/code/body-012.txt",
      "chars": 219,
      "language": "text"
    },
    {
      "file": "attachments/268ff4bc20fb/code/body-013.txt",
      "chars": 229,
      "language": "text"
    },
    {
      "file": "attachments/268ff4bc20fb/code/body-014.txt",
      "chars": 264,
      "language": "text"
    },
    {
      "file": "attachments/268ff4bc20fb/code/body-015.txt",
      "chars": 289,
      "language": "text"
    },
    {
      "file": "attachments/268ff4bc20fb/code/body-016.txt",
      "chars": 259,
      "language": "text"
    },
    {
      "file": "attachments/268ff4bc20fb/code/body-017.txt",
      "chars": 147,
      "language": "text"
    },
    {
      "file": "attachments/268ff4bc20fb/code/body-018.txt",
      "chars": 233,
      "language": "text"
    },
    {
      "file": "attachments/268ff4bc20fb/code/body-019.txt",
      "chars": 299,
      "language": "text"
    },
    {
      "file": "attachments/268ff4bc20fb/code/body-020.txt",
      "chars": 135,
      "language": "text"
    },
    {
      "file": "attachments/268ff4bc20fb/code/body-021.txt",
      "chars": 164,
      "language": "text"
    },
    {
      "file": "attachments/268ff4bc20fb/code/body-022.txt",
      "chars": 133,
      "language": "text"
    }
  ],
  "images": [
    {
      "status": "ok",
      "url": "https://cdn.hashnode.com/res/hashnode/image/upload/v1769966793110/54f69233-4b29-4d0e-825f-837fd7caa487.png",
      "bytes": 645842,
      "reused": true,
      "local_path": "attachments/268ff4bc20fb/images/000.png",
      "src": "https://cdn.hashnode.com/res/hashnode/image/upload/v1769966793110/54f69233-4b29-4d0e-825f-837fd7caa487.png",
      "alt": "How I Reduced Our React Bundle by 62%: Lessons in Real-World Performance Engineering",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://cdn.hashnode.com/res/hashnode/image/upload/v1769959819200/b50b1852-3f4c-48cc-be60-f00f101f042d.png",
      "bytes": 26312,
      "reused": true,
      "local_path": "attachments/268ff4bc20fb/images/001.png",
      "src": "https://cdn.hashnode.com/res/hashnode/image/upload/v1769959819200/b50b1852-3f4c-48cc-be60-f00f101f042d.png",
      "alt": "",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://miro.medium.com/v2/resize:fit:700/1*KNML3ng4UvgbWC4KChUohQ.png",
      "bytes": 110554,
      "reused": true,
      "local_path": "attachments/268ff4bc20fb/images/002.png",
      "src": "https://miro.medium.com/v2/resize:fit:700/1*KNML3ng4UvgbWC4KChUohQ.png",
      "alt": "",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://miro.medium.com/v2/resize:fit:700/1*fXvqHWr88Jq2pyLqQ2Jo-Q.png",
      "bytes": 130826,
      "reused": true,
      "local_path": "attachments/268ff4bc20fb/images/003.png",
      "src": "https://miro.medium.com/v2/resize:fit:700/1*fXvqHWr88Jq2pyLqQ2Jo-Q.png",
      "alt": "",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://miro.medium.com/v2/resize:fit:700/1*JQMHyc2Y85KX1U5C_P-gmQ.png",
      "bytes": 231944,
      "reused": true,
      "local_path": "attachments/268ff4bc20fb/images/004.png",
      "src": "https://miro.medium.com/v2/resize:fit:700/1*JQMHyc2Y85KX1U5C_P-gmQ.png",
      "alt": "",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://cdn.hashnode.com/res/hashnode/image/upload/v1769959971174/0db138d3-7425-4230-90f4-7835f5ee9f65.png",
      "bytes": 28478,
      "reused": true,
      "local_path": "attachments/268ff4bc20fb/images/005.png",
      "src": "https://cdn.hashnode.com/res/hashnode/image/upload/v1769959971174/0db138d3-7425-4230-90f4-7835f5ee9f65.png",
      "alt": "",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://miro.medium.com/v2/resize:fit:700/1*cQaZ1TOggstXi9lelwmQ4w.png",
      "bytes": 244764,
      "reused": true,
      "local_path": "attachments/268ff4bc20fb/images/006.png",
      "src": "https://miro.medium.com/v2/resize:fit:700/1*cQaZ1TOggstXi9lelwmQ4w.png",
      "alt": "",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://miro.medium.com/v2/resize:fit:700/1*LAPwZxK-tmy3T0dVdKpzlA.png",
      "bytes": 112585,
      "reused": true,
      "local_path": "attachments/268ff4bc20fb/images/007.png",
      "src": "https://miro.medium.com/v2/resize:fit:700/1*LAPwZxK-tmy3T0dVdKpzlA.png",
      "alt": "",
      "downloaded": true
    }
  ],
  "audio": [],
  "videos": [],
  "links": [
    {
      "href": "https://jaivalsuthar.hashnode.dev/how-i-reduced-our-react-bundle-by-62-lessons-in-real-world-performance-engineering.md",
      "label": "View as Markdown",
      "type": "a"
    },
    {
      "href": "https://hashnode.com/@JaivalSuthar",
      "label": "J",
      "type": "a"
    },
    {
      "href": "https://x.com/Jaival_Suthar",
      "label": "",
      "type": "a"
    },
    {
      "href": "https://www.linkedin.com/in/jaival-suthar-57195b257/",
      "label": "",
      "type": "a"
    },
    {
      "href": "https://github.com/btd/rollup-plugin-visualizer",
      "label": "rollup-plugin-visualizer",
      "type": "a"
    },
    {
      "href": "https://react-icons.github.io/react-icons/",
      "label": "React Icons",
      "type": "a"
    },
    {
      "href": "https://vitejs.dev/guide/build.html",
      "label": "Vite Build Optimizations",
      "type": "a"
    },
    {
      "href": "https://web.dev/performance-budgets-101/",
      "label": "Web.dev Performance Budgets",
      "type": "a"
    },
    {
      "href": "https://jaivalsuthar.hashnode.dev/tag/reactjs",
      "label": "# reactjs",
      "type": "a"
    },
    {
      "href": "https://jaivalsuthar.hashnode.dev/tag/javascript",
      "label": "# javascript",
      "type": "a"
    },
    {
      "href": "https://jaivalsuthar.hashnode.dev/tag/performance",
      "label": "# performance",
      "type": "a"
    },
    {
      "href": "https://jaivalsuthar.hashnode.dev/tag/vite",
      "label": "# vite",
      "type": "a"
    },
    {
      "href": "https://jaivalsuthar.hashnode.dev/tag/software-engineering",
      "label": "# software-engineering",
      "type": "a"
    },
    {
      "href": "https://jaivalsuthar.hashnode.dev/tag/frontend-development",
      "label": "# frontend-development",
      "type": "a"
    }
  ],
  "retry_at": "2026-10-03T08:11:01",
  "repair_version": 2,
  "repaired_at": "2026-10-14T10:51:05Z",
  "source_url": "https://jaivalsuthar.hashnode.dev/how-i-reduced-our-react-bundle-by-62-lessons-in-real-world-performance-engineering",
  "papers": [],
  "actual_video_files": [],
  "body_chars": 20106,
  "expected_body_images": 8,
  "discovery_status": "ok",
  "delivery_status": "ok",
  "missing_reason": "",
  "raw_html": "attachments/268ff4bc20fb/pages/source.html",
  "local_html": "attachments/268ff4bc20fb/pages/article.html",
  "body_base_path": "attachments/268ff4bc20fb/pages/",
  "body_markdown_path": "attachments/268ff4bc20fb/pages/article.md"
}