Profiling and Speeding Up a TypeScript Web App: A 4-Step Routine Inspired by Phone Cleanups
performanceoptimizationbundling

Profiling and Speeding Up a TypeScript Web App: A 4-Step Routine Inspired by Phone Cleanups

UUnknown
2026-03-01
10 min read
Advertisement

A practical 4-step routine to speed up TypeScript web apps: analyze bundles, prune deps, cache smartly, and profile runtime for real gains.

Feeling the drag? Speed up a TypeScript web app with a familiar 4-step routine

If your TypeScript web app feels sluggish in 2026 — slow cold starts, big JS payloads, unpredictable runtime spikes — you don't need a rewrite. Treat your app like a phone that needs a focused cleanup: inspect, prune, cache, and profile. This article maps that four-step phone routine to a practical, repeatable workflow for TypeScript projects: bundle analysis, dependency pruning, caching & service workers, and runtime profiling. Follow it, automate the checks into CI, and you’ll ship faster pages with fewer regressions.

Why this matters in 2026 (short version)

Bundlers and runtimes evolved rapidly through 2024–2026: esbuild and SWC-based toolchains, Vite/Turbopack-style dev servers, and edge-first deployments are mainstream. That makes build times faster, but it also makes it easier to accidentally ship fat bundles and misconfigure caching. A routine that pairs automatic analysis with manual pruning and profiling catches both build-time and runtime issues.

The 4-step routine (executive summary)

  1. Bundle analysis: Find what’s in the shipped bundle (entry points, vendor, polyfills).
  2. Dependency pruning: Remove underused libraries, replace heavy modules with lighter alternatives, and enforce package hygiene.
  3. Caching & service workers: Let the network and edge CDNs do the heavy lifting and secure offline-friendly caching for assets and API responses.
  4. Runtime profiling: Measure what users actually experience (CPU, memory, long tasks, paints) and fix the hotspots.

Step 1 — Bundle analysis: open the hood

The goal is simple: know what you ship. Modern toolchains emit rich metadata you can analyze programmatically. Start with a local one-off inspection; then automate it in CI.

Tools to use (2026 landscape)

  • esbuild/SWC metafiles — extremely fast metadata outputs for build graphs.
  • rollup-plugin-visualizer or webpack-bundle-analyzer — good for interactive treemaps.
  • source-map-explorer & analyze-bundle — map minified bytes back to sources.
  • Bundlephobia-style checks and automated size budgets in CI.

Example: get an esbuild metafile then inspect

# build with esbuild and emit a metafile
esbuild src/main.tsx --bundle --format=esm --metafile=meta.json --outfile=dist/app.js

# analyze (node script or use the CLI tool that reads meta.json)
node scripts/inspectMetafile.js meta.json

A short Node script can parse the metafile and print the top 20 contributors by output bytes. Integrate this into a PR check to fail builds that increase the total by more than X%.

What to look for

  • Large vendor chunks or single modules making up >30% of the bundle.
  • Unexpected polyfills or compiled helpers (look for large core-js or regenerator bundles — maybe you can target newer browsers to drop them).
  • Multiple copies of the same library due to mismatched versions (React, lodash, date-fns).
  • Big images or fonts mistakenly imported into the JS bundle.
Rule of thumb: If a module is responsible for a third of the client payload, either lazy-load it or replace it.

Step 2 — Dependency pruning: remove the bloat

Phone analogy: delete rarely used apps and stop background refresh. In web apps, do the same with packages, heavy utilities, and unused code paths. This step is both manual and automatable.

Automated checks

  • depcruise or madge to detect dependency cycles and unused files.
  • npm/prune and pnpm dedupe to reduce duplicate versions. Prefer pnpm to keep disk + install times low.
  • Integrate a bundle-size budget into CI (fail the PR if main bundle + vendor > target).

Manual strategies (high ROI)

  1. Prefer named imports and ESM entrypoints to enable tree-shaking (avoid default importing entire libraries when you need specific functions).
  2. Check for accidental CJS imports — they often block tree-shaking. Use package.json "exports" and ESM bundles where possible.
  3. Replace heavy libs with focused alternatives: lodash -> lodash-es or native methods; moment -> date-fns / Temporal polyfill; large UI frameworks -> micro-libraries or custom components.
  4. Lazy-load rarely used routes or features with dynamic import() and Suspense (or route-based units in your framework).
  5. Mark sideEffects:false in package.json for your packages or monorepo workspaces when safe — this helps bundlers tree-shake unused exports.

TypeScript-specific tips

  • Exclude test files and dev-only utilities from the production build (tsconfig’s exclude & your bundler config).
  • Use importsNotUsedAsValues and preserveValueImports to avoid runtime imports when types are used only for compilation.
  • Enable incremental and composite builds for faster CI and local builds, but keep emitted artifacts out of your final bundle using proper bundler inputs.
// tsconfig.json snippet (pruning-friendly)
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "importsNotUsedAsValues": "error",
    "preserveValueImports": false,
    "incremental": true,
    "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo"
  },
  "exclude": ["**/*.spec.ts", "tests/**", "scripts/**"]
}

Step 3 — Caching & Service Workers: make the network work for you

On phones we offload to background updates and cached app data; on web apps we rely on CDNs, HTTP caching, and optionally service workers. The aim is to reduce cold-downloads and make page navigations fast and resilient.

Quick wins

  • Set long-lived Cache-Control for immutable assets (hashed filenames) and short-lived for HTML/JSON endpoints.
  • Use a CDN that supports HTTP/3 and edge caching for both assets and SSR/edge responses.
  • Preload critical resources (<link rel="preload">) for fonts and hero images.

Service worker strategy (TypeScript example)

Use a small TypeScript service worker to precache critical assets and add runtime stale-while-revalidate for API responses. Workbox remains helpful in 2026 for generating precache manifests, but in many modern apps a lightweight hand-rolled SW is preferred for predictability.

// src/sw.ts (TypeScript service worker — compile/sw to JS during build)
const CACHE_NAME = 'app-v1';
const PRECACHE_URLS = [
  '/',
  '/index.html',
  '/app.js',
  '/styles.css'
];

self.addEventListener('install', (event: ExtendableEvent) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then((cache) => cache.addAll(PRECACHE_URLS))
  );
  self.skipWaiting();
});

self.addEventListener('activate', (event: ExtendableEvent) => {
  event.waitUntil(self.clients.claim());
});

self.addEventListener('fetch', (event: FetchEvent) => {
  const url = new URL(event.request.url);

  // API: stale-while-revalidate
  if (url.pathname.startsWith('/api/')) {
    event.respondWith(
      caches.open(CACHE_NAME).then((cache) =>
        cache.match(event.request).then((cached) => {
          const network = fetch(event.request).then((resp) => {
            cache.put(event.request, resp.clone());
            return resp;
          });
          return cached || network;
        })
      )
    );
    return;
  }

  // Static assets: cache-first
  if (url.pathname.endsWith('.js') || url.pathname.endsWith('.css')) {
    event.respondWith(
      caches.match(event.request).then((resp) => resp || fetch(event.request))
    );
  }
});

Build toolchains like Vite and Turbopack often provide plugins (or simple asset manifests) to generate precache lists automatically. For edge deployments, configure your CDN to respect origin Cache-Control headers and consider stale-while-revalidate at the CDN layer.

When not to use a service worker

If your app is a thin client with sensitive real-time data or you rely entirely on server-side rendering with edge caching, a service worker can add complexity. Prefer CDN + edge caching + short TTLs. Use SWs when you need offline capability, deterministic caching behavior, or fine-grained control.

Step 4 — Runtime profiling: find the hot paths

After trimming bytes, measure runtime. Cold bundle size doesn't tell the whole story — long tasks, heavy paint operations, and memory churn hurt perceived performance.

Tools & data sources

  • Chrome DevTools Performance and the React Profiler for UI frameworks.
  • Lighthouse and WebPageTest for lab measurements.
  • RUM via Web Vitals, OpenTelemetry, or Sentry Real User Monitoring for field data.
  • Playwright/pytest-benchmark for scripted, repeatable traces in CI.

Measure from the user’s perspective

Capture First Contentful Paint, Largest Contentful Paint, Cumulative Layout Shift, and Time to Interactive. But also record CPU usage and long tasks which correlate with janky scrolling and input delay.

TypeScript-friendly instrumentation example

// src/perf.ts — small wrapper that sends marks to your RUM
export function markStart(name: string) {
  if (typeof performance !== 'undefined' && performance.mark) {
    performance.mark(`${name}-start`);
  }
}

export function markEnd(name: string) {
  if (typeof performance !== 'undefined' && performance.mark && performance.measure) {
    performance.mark(`${name}-end`);
    performance.measure(name, `${name}-start`, `${name}-end`);
    const measure = performance.getEntriesByName(name)[0];
    // send to RUM collector (example)
    if (measure) {
      navigator.sendBeacon('/rum', JSON.stringify({ name, duration: measure.duration }));
    }
  }
}

Wrap expensive component mounts or data-fetch critical paths with these marks. Collect traces over time and prioritize fixes where both high duration and high user impact overlap.

Server-side profiling (for full-stack TS apps)

Use Node’s built-in profiler, clinic.js, or the profiler included with your host. For edge functions, record execution time and cold-start statistics. 2025–2026 trends pushed many teams to edge-first SSR — watch cold-starts and ephemeral cache misses.

Putting it together: integrate into CI/CD

Make the routine repeatable. A simple GitHub Actions pipeline can run bundle analysis, enforce budgets, run unit tests, and summarize profiler traces for PR reviewers.

# .github/workflows/ci.yml (sketch)
name: CI
on: [push, pull_request]

jobs:
  build-and-analyze:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install
        run: pnpm install --frozen-lockfile
      - name: Build (esbuild)
        run: pnpm build
      - name: Bundle analysis
        run: pnpm analyze-bundle --meta dist/meta.json --threshold 200000
      - name: Run tests
        run: pnpm test -- --coverage

When the analysis step detects a regression, fail the build and attach a treemap report. Keep the threshold tight but realistic — allow small growths for feature additions.

Advanced strategies and 2026 predictions

Look forward with three practical trends to ride:

  • Bundlerless and native modules: More apps will selectively ship native ESM modules and use import maps for low-latency loads. When possible, prefer code-splitting with native imports to eliminate a secondary bundling step for tiny features.
  • Edge-first caching patterns: Expect more granular edge function caches and region-aware routing — push SSR caching logic to the edge and use short revalidation windows to keep payloads fresh without re-rendering everything.
  • Automated size budgets and AI-assisted suggestions: CI will not just fail builds — it will suggest smaller alternatives, auto-suggest named imports, or propose code-splits via PR comments (we’re already seeing early tooling in late 2025).

Common pitfalls and how to avoid them

  • Relying only on bundle size: smaller bytes don’t guarantee smooth frames. Always pair with runtime profiling.
  • Overzealous caching: aggressive caching of dynamic JSON can show stale data. Pair service workers with cache-busting strategies and validation endpoints.
  • Removing packages without user data: prune based on usage metrics, not just gut feelings — use RUM and source-usage telemetry to guide removals.
  • Ignoring dev experience: aggressive tree-shaking or transformer settings can slow developer rebuilds. Use incremental builds and fast dev servers (esbuild/SWC/Turbopack) to keep DX snappy.

Actionable checklist (ready to copy to a new project)

  1. Run a bundle analysis and save the report: identify top 10 contributors.
  2. Automate duplicate-package detection (pnpm dedupe / npm dedupe). Lock package versions with lockfile.
  3. Replace or lazy-load the biggest module(s); mark sideEffects=false where safe.
  4. Implement hashed filenames + long Cache-Control in CDN for static assets; set HTML to short TTL.
  5. Add a tiny TypeScript service worker for precache + stale-while-revalidate for APIs.
  6. Instrument key UI flows with performance.mark/measure and collect via RUM.
  7. Integrate bundle-budget checks into CI and fail PRs on regressions.

Final takeaway

Think of your TypeScript app like a phone: periodic, focused maintenance keeps it feeling new. The four-step routine — analyze, prune, cache, and profile — turns a one-off sprint into a repeatable lifecycle that integrates with modern 2026 toolchains and edge-first deployments. Small, measurable improvements compound: shaving 20–40KB here and eliminating a few long tasks there produces noticeably snappier pages for real users.

Try it now

Pick one route: run an esbuild/webpack bundle analysis today, and set up a CI budget. If you want a starter checklist or a sample GitHub Action to copy, clone our minimal template and drop it into your repo to start catching regressions on PRs.

Ready to make your TypeScript app feel brand new? Start with a bundle report, prune one heavy dependency, add a tiny service worker, and measure a real user flow. Share your before/after results — we’ll help interpret them.

Call to action

Subscribe to our TypeScript tooling newsletter for bite-sized tactics, or open a PR with your bundle analysis report on our template repo to get feedback from the community.

Advertisement

Related Topics

#performance#optimization#bundling
U

Unknown

Contributor

Senior editor and content strategist. Writing about technology, design, and the future of digital media. Follow along for deep dives into the industry's moving parts.

Advertisement
2026-03-01T01:16:30.578Z