Skip to content

Last updated:

Bundle Optimization

CPR uses Nuxt 4 with Vite as the build tool. Several configuration choices in nuxt.config.ts and architectural patterns keep the production bundle small and efficient.

Production Console and Debugger Removal

In production builds, console.* calls and debugger statements are automatically stripped via esbuild:

ts
// nuxt.config.ts
vite: {
  esbuild: {
    drop:
      process.env.NODE_ENV === 'production' ? ['console', 'debugger'] : [],
  },
},

This reduces bundle size and prevents sensitive information from leaking into the browser console in production. During development, all console.log and debugger statements work normally.

Vite Tree-Shaking

Vite uses Rollup for production builds, which performs aggressive tree-shaking. This means unused exports are automatically removed from the final bundle. CPR benefits from this in several ways:

Icon Imports

CPR imports icons individually from @heroicons/vue, which is fully tree-shakeable:

ts
// Good -- only XMarkIcon is included in the bundle
import { XMarkIcon } from '@heroicons/vue/24/outline';

// Avoid -- would import the entire icon library
import * as Icons from '@heroicons/vue/24/outline';

Each icon is roughly 1-2 KB. Importing only what is needed across the application can save hundreds of KB compared to importing the full icon set.

Validation Rules

The useValidationRules module exports individual functions. Pages only bundle the rules they use:

ts
// Only 'required' and 'email' are bundled for this page
import { required, email } from '~/composables/useValidationRules';

Nuxt Auto-Imports

Nuxt 4 auto-imports Vue APIs, composables, and components, eliminating the need for explicit import statements. This has a bundle benefit: Nuxt's build process can statically analyze which auto-imports are actually used per page and avoid including unused ones.

Auto-imported APIs include:

ts
// These are available without import statements:
// Vue: ref, reactive, computed, watch, onMounted, etc.
// Nuxt: useRuntimeConfig, navigateTo, defineNuxtRouteMiddleware, etc.
// Vue Router: useRoute, useRouter
// Pinia stores (by convention)

The components configuration in nuxt.config.ts auto-imports all components from app/components/ without path prefixes:

ts
// nuxt.config.ts
components: [
  {
    path: '~/components',
    pathPrefix: false,
  },
],

Hidden Source Maps

Source maps are generated as hidden in production:

ts
// nuxt.config.ts
sourcemap: {
  client: 'hidden',
},

This means:

  • Source maps are generated for error tracking tools like Sentry.
  • They are not served to end users or linked in the JavaScript files.
  • Users cannot see the original source code in browser DevTools.
  • Sentry can still symbolicate stack traces for meaningful error reports.

Pinia Stores: Composition API Pattern

All CPR Pinia stores use the Composition API (setup function) pattern instead of the Options API. This is more tree-shakeable because Rollup can statically analyze and remove unused store properties:

ts
// Composition API -- tree-shakeable
export const useInsuranceStore = defineStore('insurance', () => {
  const list = ref<InsuranceResource[]>([]);
  const pagination = ref<APIPagination>({ ... });
  return { list, pagination };
});

// Options API -- less tree-shakeable
export const useInsuranceStore = defineStore('insurance', {
  state: () => ({ list: [], pagination: {} }),
});

Analyzing the Bundle

To understand what is in the production bundle, use Rollup's built-in visualizer:

bash
# Build with bundle analysis
npx nuxt build --analyze

This generates an interactive treemap showing:

  • Which modules contribute the most to bundle size
  • Whether large dependencies are being fully or partially included
  • Opportunities for lazy loading or replacing heavy libraries

Optimization Checklist

PracticeStatus in CPR
Route-based code splittingAutomatic via Nuxt
Tree-shakeable icon imports@heroicons/vue individual imports
Production console removalesbuild.drop configured
Hidden source mapssourcemap.client: 'hidden'
Auto-imports (no unused import overhead)Nuxt auto-imports enabled
Composition API storesAll stores use setup function
Component auto-import without prefixespathPrefix: false configured

What to Watch For

  • Large third-party dependencies -- Before adding a new npm package, check its bundle size on bundlephobia.com. Consider alternatives or dynamic imports for heavy libraries.
  • Barrel file re-exports -- Avoid export * from patterns that can defeat tree-shaking. Import directly from the specific module.
  • Unintended polyfills -- Vite targets modern browsers by default. Ensure the target is not set too broadly, which would include unnecessary polyfills.
  • Duplicate dependencies -- Run npm ls <package> to check for multiple versions of the same library being bundled.

CPR - Clinical Patient Records