ScreenshotNeo

BlogHow-to

How to Reduce JavaScript and Improve Page Load Time

Find the scripts that delay rendering or input, then remove, split, or reschedule them and check the result in both lab and field data.

By the ScreenshotNeo team4 October 202610 min read

To reduce JavaScript and improve page load time, first identify which scripts delay rendering or occupy the main thread. Then remove code the site does not need, split code needed later from the initial route, and load remaining scripts according to their execution requirements. Measure each change: a smaller download alone does not guarantee a faster page. JavaScript costs include transfer, parsing, compilation, execution, and contention with rendering and user input. The right fix depends on which cost is slowing your page.

This guide shows how to diagnose the cause, make targeted changes, and check whether users benefit. The same method applies whether the page uses a bundler, framework, server-rendered HTML, or a few script tags.

1. Measure where the JavaScript work happens

Start with a representative route and repeatable conditions. In Chrome DevTools, open Network, enable Disable cache for a cold-load inspection, reload, and filter by JS. Record the largest files, transfer sizes, request timing, and which origins provide them. Then use the Performance panel or a Lighthouse run to find long script tasks and expensive parse, compile, or evaluation work.

  1. Inspect the Network waterfall. Note large bundles, scripts requested early, slow third-party origins, and requests that start only after other JavaScript runs.
  2. Use the Coverage panel to see which portions of loaded files were unused during that recording. Repeat across routes and important interactions before deciding that code is safe to remove.
  3. Use Lighthouse diagnostics to locate unused JavaScript and costly execution. Treat these as leads: one lab run samples one environment and one path through the page.
  4. Write down a baseline for the same route, device class, and test conditions. Change one cause at a time, then repeat the measurement.

Coverage is evidence about the session it observed, not proof that a function is never used. Exercise menus, forms, dialogs, account flows, and other states before removing code. Measure a production build where possible; development builds and source maps can distort the picture.

2. Remove JavaScript the page does not need

Audit dependencies and features against actual product requirements. Remove unused packages, duplicate utilities, obsolete experiments, dead compatibility layers, and widgets without clear value. Check every route and relevant interaction first. A dependency that appears unused on the landing page may be required by checkout or a dialog.

Also check whether two packages provide overlapping features and whether a large package is imported wholesale when only a small function is needed. Prefer a supported modular entry point where the library offers one. Verify the production output after tree shaking; build configuration and package side effects can prevent apparently unused exports from disappearing.

Removal can reduce network transfer and the browser’s parse, compile, memory, and execution work. It may also reduce contention with rendering. However, if the observed delay comes from image discovery, server response, CSS, or another bottleneck, JavaScript cleanup may not change the page’s primary loading outcome.

3. Split startup code from features needed later

Send the code required to render and use the initial route first. Load route-specific or interaction-specific features when they are needed. This is code splitting: code remains available to the application but is left out of the startup payload until a route or action requires it. Dynamic import() is supported by modern JavaScript bundlers and is commonly used for this.

// Before: the editor is downloaded with the initial route.
import { openEditor } from './editor.js';

editButton.addEventListener('click', () => {
  openEditor();
});

// After: fetch the editor only when the user opens it.
editButton.addEventListener('click', async () => {
  editButton.disabled = true;
  try {
    const { openEditor } = await import('./editor.js');
    openEditor();
  } catch (error) {
    console.error('Could not load the editor', error);
    showMessage('The editor could not be loaded. Please try again.');
  } finally {
    editButton.disabled = false;
  }
});

This is runnable in a browser project with an ES module bundler that supports dynamic imports; editButton and showMessage should refer to elements and feedback functions in the application. For a router, apply the same idea at route boundaries so a page does not download every other route’s code.

Choose the split based on when code is needed, not a goal that every output file be tiny. One enormous chunk can delay startup and invalidate caching when any part changes. Too many tiny chunks can add request overhead, especially on constrained networks. Shared vendor chunks can help repeat visits when dependencies change less often, but verify actual cache behavior and loading waterfalls in the deployed build.

If client-side rendering requires a large JavaScript payload before meaningful content appears, consider whether server-rendering meaningful initial markup would let the browser render content sooner. That is an architectural change, so measure its costs and benefits against the current rendering path. See web.dev’s code-splitting guidance.

4. Choose between async and defer deliberately

A classic external script without either attribute pauses HTML parsing while the browser fetches and executes it. async and defer allow the browser to download while parsing continues, but their execution behavior differs.

Script form Download and execution Use when Watch for
<script src="..."> Parsing pauses for fetch and execution. The script truly must run at that exact point before parsing proceeds. It can delay document construction and rendering.
<script async src="..."> Downloads in parallel, executes as soon as ready; order is not guaranteed. The script is independent and may run as soon as it arrives, such as a standalone analytics loader when its requirements permit. Execution can interrupt parsing, and dependency order can break.
<script defer src="..."> Downloads in parallel, runs after parsing completes, preserving document order; before DOMContentLoaded. Scripts need document parsing to finish or depend on one another in source order. Check that no earlier inline code expects the script before it executes.
<!-- Independent script: execution order is not guaranteed. -->
<script async src="/analytics.js"></script>

<!-- Ordered application scripts: execute after parsing, in document order. -->
<script defer src="/vendor.js"></script>
<script defer src="/app.js"></script>

These examples are plain HTML and can be used directly. Do not add async to scripts that rely on another script having already initialized a global. Do not assume async makes execution free: once downloaded, the code still uses CPU and can interrupt parsing. Module scripts have their own deferred-by-default behavior; check the script type and browser semantics used by the application. For more detail, see the web.dev guide to loading third-party JavaScript.

5. Load third-party JavaScript efficiently

Inventory analytics, ads, chat, A/B testing, social embeds, video players, and tag-manager injections. For each one, ask what site value it provides, who owns it, which pages need it, and whether it must run during initial rendering. Remove scripts without a clear purpose. For valuable scripts, load them later or only on pages and interactions that need them.

  • Use defer for noncritical scripts that need predictable document order; use async only when independent early execution is appropriate.
  • Delay below-the-fold embeds until they are near view, or use a user-initiated placeholder for content such as a video where that fits the experience. Preserve a usable fallback if the loading code fails.
  • Consider preconnect only for a small number of critical origins that the page will use soon. It starts connection setup early; speculative connections to unused origins waste resources.
  • Self-hosting can give more control over caching and delivery, but it creates a maintenance obligation to track vendor updates and security fixes. Keep the vendor-hosted version if operational control is not worth that burden.

Asynchronous loading does not erase the execution cost of many scripts. Third-party code can create network requests, main-thread work, and behavior outside your release cycle, so measure it by origin as well as by bundle. web.dev’s third-party script guidance covers async, defer, early connections, and lazy loading.

6. Validate with lab and field data

Lab tools such as Lighthouse help reproduce issues and catch regressions during development. Field data shows how real visitors experience the site across devices, networks, routes, and interactions. They answer different questions: use lab diagnostics to find likely causes; use field results to determine whether users improved.

Core Web Vitals are outcomes, not a promise that any one JavaScript technique will improve a score. The current guidance lists good 75th-percentile targets of LCP at or below 2.5 seconds, INP at or below 200 milliseconds, and CLS at or below 0.1. Review mobile and desktop separately where possible. CrUX field data informs tools such as PageSpeed Insights and Search Console; detailed per-pageview diagnosis may require your own real-user monitoring. See web.dev’s Web Vitals overview and its explanation of lab and field differences.

INP reflects responsiveness across user interactions. A Lighthouse page load without meaningful interaction cannot directly measure field INP. Total Blocking Time (TBT) is a lab diagnostic and proxy for startup main-thread blocking; it is not the same metric as field INP. Track the field metric alongside lab signals rather than treating one as a substitute for the other.

7. A practical optimization sequence

  1. Capture a baseline: choose important routes, repeatable device and network conditions, and both cold and repeat visits if caching matters.
  2. Rank causes: identify scripts with large transfer, expensive evaluation, parser blocking, or third-party execution. Tie each to a user-facing feature.
  3. Remove proven dead weight: verify across routes and interactions before deleting code.
  4. Split later work: defer route, dialog, editor, and other non-startup features until their trigger.
  5. Fix scheduling: select async or defer from dependency and timing requirements; remove render-path third parties when possible.
  6. Retest functionality and performance: inspect errors, delayed interactions, network requests, and lab diagnostics.
  7. Check field outcomes: monitor Core Web Vitals and relevant route/device segments after deployment.

Troubleshooting common JavaScript performance fixes

Symptom Likely cause What to check or fix
Page is still slow after reducing bundle bytes Main-thread execution, server response, images, CSS, or resource discovery is the bottleneck. Inspect Performance traces and the Network waterfall. Find the delay before applying another bundle change.
A feature fails after switching to async Scripts that depend on each other ran in an unexpected order. Use defer for ordered classic scripts or explicitly coordinate initialization.
Content briefly appears incorrectly or is hidden An early third-party script or A/B test changes rendering when it executes. Review whether it must run before paint; delay or remove it when possible and preserve readable default content.
Dynamic import fails in production Chunk URL, deployment, permissions, or stale cached HTML references a missing chunk. Check the failed request and deployment asset paths. Deploy HTML and hashed assets consistently and handle the rejected import with a retry or fallback.
Coverage shows most code as unused The recording did not exercise all routes and states. Repeat on representative pages and interactions. Treat coverage as a sample, not a deletion list.
More chunks made loading worse Additional request overhead or chunk dependencies outweigh reduced startup work. Inspect the waterfall and consolidate where measurements show excessive requests; preserve useful route-level deferral.
Lab score improved but visitors report no change The lab run did not match field devices, networks, routes, or interactions, or the change did not affect the limiting metric. Compare field data by page and device, and add real-user telemetry if aggregate data lacks needed detail.

Performance, reliability, and cost considerations

JavaScript reduction can save mobile data and CPU, and can leave more main-thread time for rendering and interaction. The actual value depends on the devices and networks your visitors use. Code splitting trades startup transfer for later requests; caching can make repeat visits cheaper, while cache invalidation or many small chunks can add overhead.

Reliability matters alongside speed. A feature loaded on demand can fail on a flaky network, so show a recoverable error state and avoid making essential content depend on a nonessential script. Third-party changes can alter execution cost without a code release from your team. Maintain an inventory and recheck after vendor or tag-manager changes.

There is no universal speed gain or fixed cost saving from removing, splitting, or rescheduling JavaScript. Compare the measured improvement with engineering and maintenance cost, retained functionality, added requests, and field outcomes.

Or skip the browser setup

For an automated screenshot of the page before and after a performance change, ScreenshotNeo is a website screenshot API and MCP server. One GET request captures a URL as PNG, JPEG, WebP, or PDF; see the API documentation.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
r.raise_for_status()
with open("shot.webp", "wb") as image:
    image.write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const image = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', image));

Cookie banners, popups, and chat widgets are removed before the shot. Bot checks, blank pages, and failed loads are never billed; response headers report the page verdict and billing status. An MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Sign up for free and capture 1,000 screenshots a month with no card.

FAQ

Will minifying JavaScript make my page fast?

Minification can reduce transfer size, but it does not remove the need to parse, compile, and execute the remaining code. Measure which cost is limiting the page.

Should every script use defer?

No. Use it when execution should wait until parsing completes and document order matters. Independent scripts that can run as soon as available may use async, but can still interrupt parsing.

Does a good Lighthouse score prove field INP is good?

No. Lighthouse is a lab run; INP depends on real interactions. Use field data to assess responsiveness visitors experience.

How often should I recheck script performance?

Recheck after dependency, route, tag-manager, or third-party changes, and monitor field performance continuously if the page is important to the business.