Zum Inhalt springen
Core Web Vitals specialists
All Articles Performance

JavaScript and Performance Budgets: Less Code, Faster Websites

14 min read
JavaScriptPerformance BudgetsINPCore Web Vitals

JavaScript is the most expensive resource type on the web. While images only need to be downloaded and decoded, JavaScript must be downloaded, parsed, compiled and executed -- each of these steps blocks the browser's main thread and delays page interactivity. According to HTTP Archive, websites load a median of 509 KB of compressed JavaScript per page view (Source: HTTP Archive, 2025), which often equals 1.5 to 2.5 MB uncompressed. The result: 70 percent of websites fail the recommended INP threshold of 200 milliseconds (Source: Chrome UX Report, 2025). This article shows how performance budgets, code splitting and targeted third-party management reduce JavaScript load and sustainably improve Core Web Vitals.

JavaScript Performance Budget PipelineSource Bundle1.8 MB JavaScript (uncompressed)145 npm packagesTBT: 3200ms | INP: 450msOptimizationTree ShakingCode SplittingDead Code EliminationOptimized Bundle380 KB JavaScript (compressed)42 npm packagesTBT: 280ms | INP: 85msPerformance Budget: max 150 KB JS (initial, compressed)Critical PathFramework Core: 45 KBRouter: 8 KBCritical CSS-in-JS: 12 KBTotal: 65 KBLazy ChunksProduct Page: 28 KBCheckout: 35 KBSearch: 18 KBLoaded on demandThird-PartyAnalytics: 22 KBConsent: 15 KBChat Widget: 45 KBBudget Risk!Bundle AnalysisTreemap visualizationDuplicate detectionImport cost trackingCI/CD integration-79%JS bundle reduced200msINP target150KBInitial JS budget3xfaster interactionOptimization Path1. Audit and Measure2. Define Budget3. Split and Shake4. Audit Third-Party5. CI/CD Checks

Why JavaScript Is the Most Expensive Web Resource

The difference between JavaScript and other resources lies in processing costs. A 200 KB JPEG image needs milliseconds for decoding and rendering. 200 KB of compressed JavaScript, however, must first be decompressed (typically to 600-800 KB), then parsed, compiled to bytecode and finally executed. On an average mid-range smartphone, processing 200 KB of compressed JavaScript takes 1 to 2 seconds -- seconds during which the main thread is blocked and the page cannot respond to any user input.

This main thread blocking directly affects the Interaction to Next Paint (INP), which replaced First Input Delay (FID) as a Core Web Vital in March 2024. INP measures the latency of all user interactions -- clicks, taps and keyboard inputs -- throughout the entire page visit and reports the worst interaction (at the 98th percentile). Google defines an INP under 200 milliseconds as good. JavaScript-heavy pages regularly exceed this value because long tasks block the main thread and interactions must wait.

The impact is measurable: A study by Akamai shows that each additional second of JavaScript processing time reduces conversion rates by 4.4 percent (Source: Akamai, 2024). For an online shop with 100,000 euros monthly revenue, half a second of unnecessary JavaScript processing means a potential revenue loss of 2,200 euros per month. Performance analysis of JavaScript load is thus a direct lever for business success.

Performance Budgets: Setting Limits Before It Is Too Late

A performance budget defines upper limits for resources a website may load. The concept is simple but effective: instead of reactively fixing performance problems after they occur, a budget proactively prevents them from arising in the first place. The most effective budgets define limits at three levels: file size (maximum bytes per resource type), load time (maximum metrics like LCP, TBT) and complexity (maximum number of requests or npm packages).

For JavaScript, we recommend an initial budget of maximum 150 KB compressed JavaScript for the critical path -- the code needed for the first interactive rendering of the page. This budget derives directly from the INP target: 150 KB compressed corresponds to approximately 450 KB uncompressed, which is processed in about 900 milliseconds on an average smartphone. Adding network latency and other rendering costs, there is still enough room for an INP under 200 milliseconds.

Budget enforcement ideally happens in the CI/CD pipeline. Build tools like webpack and Vite support performance hints that abort the build when a configured size limit is exceeded. Additionally, tools like Lighthouse CI can automatically run a performance audit on every pull request and issue a warning when budget limits are breached. This automation is critical -- without automatic checking, performance regressions inevitably creep in.

Size Budget

Maximum 150 KB compressed JavaScript for the critical path. Additional chunks are lazy-loaded and do not count against the initial budget. Monitored via CI/CD.

Time Budget

Total Blocking Time under 300 milliseconds, INP under 200 milliseconds. These metrics correlate directly with the perceived responsiveness of the website.

Dependency Budget

Maximum number of npm packages and maximum total size of node_modules. Prevents uncontrolled growth of the dependency chain and reduces security risks.

Code Splitting: Only Load What Is Actually Needed

Code splitting divides the JavaScript bundle into smaller chunks that are loaded individually and on demand. Instead of loading the entire application code on the initial page load, only the code needed for the current page is loaded. All other modules are loaded only when the user invokes the corresponding functionality -- such as navigating to a new page or opening a modal.

The most important form of code splitting is route-based splitting: each page or route gets its own chunk that is only loaded when the user visits that route. Modern frameworks like SvelteKit, Next.js and Nuxt implement route-based splitting automatically. For Shopware-based frontends, splitting must be manually configured by dividing plugin functionality into separate entry points.

Component-based splitting goes a step further and loads individual components only when needed. For example: a website's chat widget is only loaded when the user clicks the chat button. The review form is only loaded when the user scrolls to the review section. This technique drastically reduces initial JavaScript download but requires careful planning so that the delay from lazy loading does not impair the user experience.

Prefetching complements code splitting with predictive loading. The browser loads chunks the user will likely need soon in the background -- for example, the code for the next page when the user hovers over a link. and the Intersection Observer API enable this predictive loading without impacting current page performance. The result: the user does not experience code splitting as a delay because the needed code is already cached when they need it.

Tree Shaking: Automatically Eliminating Unused Code

Tree shaking is a build optimization that removes unused code from the final bundle. The name comes from the metaphor of shaking a tree to make dead leaves fall off. In practice, the bundler analyzes the entire code's import chain and removes all exports that are not imported anywhere. This dead code elimination can reduce bundle size by 20 to 50 percent, depending on how much unused code exists in the dependencies.

Effective tree shaking requires that all modules use the ES module format (import/export). CommonJS modules (require/module.exports) cannot be effectively tree-shaken because their exports are dynamic and only determined at runtime. When choosing npm packages, look for the presence of a module or exports field in the package.json -- this signals that the package provides ES modules.

A common pitfall is the side effect marking. Modules that execute side effects on import -- such as setting global variables or injecting CSS -- must not be removed by tree shaking, even if their exports are unused. The sideEffects property in package.json tells the bundler which modules can be safely removed. Incorrectly set or missing side effect markings are one of the most common reasons why tree shaking does not show the expected effect.

Third-Party Scripts: The Hidden Performance Killer

Own code often makes up only a fraction of a website's JavaScript load. Analytics trackers, consent managers, chat widgets, retargeting pixels, social media embeds and A/B testing tools quickly add up to hundreds of kilobytes. According to an analysis by the Web Almanac, websites load a median of 22 third-party scripts (Source: Web Almanac, 2024) that together account for 40 to 60 percent of total JavaScript load.

The problem with third-party scripts is the lack of control. Own code can be optimized, split and compressed -- third-party scripts are externally hosted and can become larger, slower or more unstable with every update, without the website operator knowing. A single poorly implemented chat widget can double the INP of an otherwise optimized website. A professional performance analysis systematically uncovers these hidden costs.

The solution starts with a complete inventory of all third-party scripts and their performance costs. For each script, the file size, main thread blocking time and impact on Core Web Vitals are documented. Each script is then assigned to one of three categories: essential (consent manager, analytics), optional with value (live chat, reviews) or dispensable (social media buttons, redundant trackers). Scripts in the third category are removed; scripts in the second category are lazy-loaded or loaded upon user interaction. This prioritization is a central component of every server and infrastructure optimization.

Script TypeTypical SizeMain Thread ImpactRecommendation
Analytics Tracker20-45 KBMedium (100-300ms)Load async, first-party proxy
Consent Manager15-80 KBHigh (200-500ms)Choose lightweight tool
Chat Widget40-200 KBVery high (500-1500ms)Lazy load on click
A/B Testing30-100 KBHigh (300-800ms)Prefer server-side testing
Social Media Buttons50-300 KBMedium (200-600ms)Use static links
Retargeting Pixels5-20 KBLow (50-150ms)Lazy load after consent

Bundle Analysis: Transparency Over Every Kilobyte

Before optimizations can take effect, the current JavaScript composition must be understood. Bundle analysis tools visualize each module's size in the bundle as a treemap, making it immediately visible which dependencies account for the largest share. Tools like webpack-bundle-analyzer, vite-bundle-visualizer and source-map-explorer create interactive treemaps showing where every byte goes.

Typical findings from a bundle analysis include: duplicated dependencies -- different versions of the same library in the bundle because transitive dependencies require different versions. Oversized imports -- a single import from a utility library pulls the entire library into the bundle because tree shaking does not take effect. Unnecessary polyfills -- polyfills for features that all target browsers already natively support. Each of these points offers concrete savings potential.

Integrating bundle analysis into the CI/CD pipeline makes performance regressions immediately visible. With every pull request, a bundle report is generated and compared with the main branch. If the bundle grows beyond the defined budget, the build is flagged and the developer must investigate the cause before the code can be merged. This feedback loop prevents the gradual growth of JavaScript load over weeks and months.

INP Optimization: Avoiding and Breaking Up Long Tasks

Interaction to Next Paint (INP) is primarily influenced by long tasks -- JavaScript tasks that block the main thread for more than 50 milliseconds. During a long task, the browser cannot respond to any user input: clicks are delayed, scrolling stutters and the page feels sluggish. Optimizing INP requires identifying and breaking up these long tasks.

The yielding pattern is the most important technique for INP improvement. Instead of executing a long computation in one go, it is broken into smaller subtasks, between which the browser has the opportunity to respond to user input. The scheduler.yield() API (available in all modern browsers from 2025) pauses the current task and gives the browser a chance to process pending interactions before the task continues.

Additional strategies for INP optimization include: keeping event handlers lean -- complex logic in event handlers should be deferred to requestAnimationFrame or requestIdleCallback. Batching state updates -- combining multiple DOM changes in a single rendering cycle instead of executing them individually. Using Web Workers -- offloading computationally intensive tasks like data processing or cryptography to a web worker that runs in its own thread and does not block the main thread.

Practical Tip: INP Debugging with the Performance API

Use the PerformanceObserver API with the 'event' type to capture all slow interactions in the field. Each entry contains the interaction latency, event type and target element. Aggregate this data to identify the pages and elements with the worst INP values. This allows you to specifically optimize the most problematic interactions.

Modern JavaScript Delivery: Modules, defer and async

How JavaScript is included affects performance as much as file size. A regular