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:
// 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:
// 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:
// 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:
// 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:
// nuxt.config.ts
components: [
{
path: '~/components',
pathPrefix: false,
},
],Hidden Source Maps
Source maps are generated as hidden in production:
// 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:
// 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:
# Build with bundle analysis
npx nuxt build --analyzeThis 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
| Practice | Status in CPR |
|---|---|
| Route-based code splitting | Automatic via Nuxt |
| Tree-shakeable icon imports | @heroicons/vue individual imports |
| Production console removal | esbuild.drop configured |
| Hidden source maps | sourcemap.client: 'hidden' |
| Auto-imports (no unused import overhead) | Nuxt auto-imports enabled |
| Composition API stores | All stores use setup function |
| Component auto-import without prefixes | pathPrefix: 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 * frompatterns 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.
