Lazy Loading
Lazy loading reduces the initial bundle size by splitting code into smaller chunks that are loaded on demand. Nuxt 4 provides automatic code splitting for routes, and Vue offers defineAsyncComponent for component-level splitting.
Automatic Route-Based Code Splitting
Nuxt automatically splits the bundle by route. Each page in app/pages/ becomes its own chunk that is only downloaded when the user navigates to that route.
app/pages/
index.vue → chunk for /
login.vue → chunk for /login
patients/
index.vue → chunk for /patients
[id].vue → chunk for /patients/:id
transactions/
index.vue → chunk for /transactions
queue/
index.vue → chunk for /queueNo configuration is needed -- this is a built-in Nuxt behavior. When a clinical staff member logs in and goes to the queue page, only the queue page's JavaScript is loaded. The transactions, patient registration, and pharmacy pages are not downloaded until they are actually visited.
Lazy Loading Components with defineAsyncComponent
For heavy components that are not immediately visible (modals, charts, complex forms), use defineAsyncComponent to defer loading:
<script setup lang="ts">
import { defineAsyncComponent } from 'vue';
// This component's code is only downloaded when the modal opens
const PatientHistoryForm = defineAsyncComponent(
() => import('~/components/patient/PatientHistoryForm.vue')
);
const showHistory = ref(false);
</script>
<template>
<UiButton @click="showHistory = true">View History</UiButton>
<UiModal v-model="showHistory" title="Patient History">
<!-- Component loads only when modal is open -->
<PatientHistoryForm v-if="showHistory" :patient-id="patientId" />
</UiModal>
</template>With Loading and Error States
For larger components, provide feedback during loading:
<script setup lang="ts">
const SurgeryScheduleCalendar = defineAsyncComponent({
loader: () => import('~/components/surgery/SurgeryScheduleCalendar.vue'),
loadingComponent: UiSkeletonLoader,
errorComponent: UiAlert,
delay: 200, // Show loading after 200ms
timeout: 10000, // Timeout after 10s
});
</script>Nuxt Lazy Component Prefix
Nuxt provides a Lazy prefix convention for auto-imported components. Prefixing a component name with Lazy automatically wraps it in defineAsyncComponent:
<template>
<!-- Eagerly loaded -- downloaded with the page chunk -->
<UiButton>Save</UiButton>
<!-- Lazily loaded -- downloaded only when rendered -->
<LazyPatientBillingHistory
v-if="showBilling"
:patient-id="patientId"
/>
</template>This works because Nuxt's components configuration in nuxt.config.ts auto-imports all components in app/components/:
// nuxt.config.ts
components: [
{
path: '~/components',
pathPrefix: false,
},
],Dynamic Imports for Heavy Dependencies
If a page uses a large third-party library (for example, a charting library for dashboard analytics), import it dynamically:
// Only download the chart library when the dashboard is viewed
const loadChart = async () => {
const { Chart } = await import('chart.js');
// Use Chart...
};
onMounted(() => {
if (showDashboard.value) {
loadChart();
}
});NuxtLink Prefetching
NuxtLink automatically prefetches the target page's chunk when the link enters the viewport. This makes navigation feel instant because the code is already downloaded by the time the user clicks.
<template>
<!-- Nuxt prefetches /patients chunk when this link is visible -->
<NuxtLink to="/patients">Patient Records</NuxtLink>
<!-- Disable prefetching for rarely-visited pages -->
<NuxtLink to="/admin/settings" :prefetch="false">Settings</NuxtLink>
</template>When to Use Lazy Loading
| Scenario | Approach |
|---|---|
| Page routes | Automatic (Nuxt handles it) |
| Modal content (patient history, forms) | defineAsyncComponent or Lazy prefix |
| Tab content not visible on load | v-if + Lazy prefix |
| Heavy third-party libraries | Dynamic import() |
| Components always visible on page | Do not lazy load (adds latency) |
What Not to Lazy Load
- UI primitives (
UiButton,UiInput,UiTable) -- These are small, used everywhere, and should be in the main bundle. - Layout components -- Always visible and needed immediately.
- Components above the fold -- Lazy loading these causes layout shift and a worse perceived performance.
Verifying Code Splitting
You can inspect the generated chunks during build:
npx nuxt build
# Check the output in .output/public/_nuxt/
# Each route and lazy component will have its own .js chunkUse browser DevTools Network tab to verify that chunks are loaded on demand as you navigate between pages.
