Performance Optimization Guide
This guide introduces strategies to optimize application performance when building with BrutxUI.
1. Tree-shaking & Imports
1.1 Entry Point Imports (Recommended)
BrutxUI supports tree-shaking natively, allowing bundlers to automatically strip unused components during compilation:
// Recommended: Tree-shaking is handled automatically by bundlers
import { Button, Input, Card } from 'brutx-ui-vue'
import 'brutx-ui-vue/styles.css'1.2 Sub-path Imports (Stable Allowlist)
The root entry is the stable component import surface for BrutxUI. Sub-path imports are supported only for the stable allowlist declared in package.json exports: button, input, dialog, toast, form, select, dropdown-menu, table, card, tabs, calendar, carousel, code-block, hooks, locales, devtools-plugin, and the style entries. Import components that are not listed here from the root entry.
// Use direct imports only for allowlisted sub-paths
import { Button, buttonVariants } from 'brutx-ui-vue/button'
import { Dialog, DialogContent } from 'brutx-ui-vue/dialog'1.3 Bundler Optimization Configurations
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
build: {
rollupOptions: {
output: {
manualChunks: {
// Separate UI library assets from main bundle chunks
'brutx-ui': ['brutx-ui-vue'],
},
},
},
},
})2. Component Lazy Loading
2.1 Async Components
Load heavy or off-screen components lazily using Vue's defineAsyncComponent:
<script setup lang="ts">
import { defineAsyncComponent } from 'vue'
// Lazy load dialog content panel
const HeavyDialog = defineAsyncComponent(() =>
import('brutx-ui-vue').then(m => m.DialogContent)
)
// Lazy load data table components
const DataTable = defineAsyncComponent(() =>
import('brutx-ui-vue').then(m => m.DataTable)
)
</script>
<template>
<Dialog>
<DialogTrigger as-child>
<Button>Open</Button>
</DialogTrigger>
<HeavyDialog>
<!-- Heavy components -->
</HeavyDialog>
</Dialog>
</template>2.2 Route Level Code Splitting
// router/index.ts
const routes = [
{
path: '/dashboard',
component: () => import('@/views/DashboardView.vue'),
},
{
path: '/settings',
// Dynamic route bundle resolution
component: () => import('@/views/SettingsView.vue'),
},
]2.3 Conditional Initialization
<script setup lang="ts">
import { ref, shallowRef } from 'vue'
const showHeavyComponent = ref(false)
const heavyData = shallowRef(null)
// Load data datasets only when required
async function loadHeavyData() {
if (!heavyData.value) {
heavyData.value = await fetchLargeDataset()
}
showHeavyComponent.value = true
}
</script>
<template>
<Button @click="loadHeavyData">Load Data</Button>
<DataTable v-if="showHeavyComponent" :data="heavyData" />
</template>3. Large Datasets (shallowRef)
3.1 Non-reactive Scopes via shallowRef
For large datasets, use shallowRef to prevent Vue from performing deep reactivity traversals across thousands of nested properties:
<script setup lang="ts">
import { shallowRef, triggerRef } from 'vue'
// Avoid deep reactive tracking on large lists
const tableData = shallowRef<DataRow[]>([])
async function loadData() {
const response = await fetch('/api/data')
tableData.value = await response.json()
}
// Trigger manual view updates after inline updates
function updateRow(index: number, newData: Partial<DataRow>) {
tableData.value[index] = { ...tableData.value[index], ...newData }
triggerRef(tableData)
}
</script>
<template>
<DataTable :data="tableData" />
</template>3.2 Virtualizing Massive Lists
Use VirtualScroll to keep DOM footprints lightweight when rendering datasets over 1000 items:
<script setup lang="ts">
import { ref } from 'vue'
import { VirtualScroll } from 'brutx-ui-vue'
const items = ref(Array.from({ length: 10000 }, (_, i) => ({
id: i,
name: `Item ${i}`,
})))
</script>
<template>
<VirtualScroll
:items="items"
:item-height="48"
:buffer="5"
class="h-[600px] border-3 border-brutal"
>
<template #default="{ item }">
<div class="px-4 py-2 border-b border-brutal-border">
{{ item.name }}
</div>
</template>
</VirtualScroll>
</template>3.3 Server Side & Local Pagination
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useDataTablePagination } from 'brutx-ui-vue'
const allData = ref<Item[]>([])
const pageSize = ref(20)
const { currentPage, totalPages, paginatedData } = useDataTablePagination({
data: allData,
pageSize,
})
</script>
<template>
<DataTable :data="paginatedData" />
<Pagination
v-model:page="currentPage"
:total="totalPages"
class="mt-4"
/>
</template>4. Debounce & Throttle
4.1 Input Search Debouncing
<script setup lang="ts">
import { ref } from 'vue'
import { useDebounce } from 'brutx-ui-vue'
const searchQuery = ref('')
const [debouncedSearch, isDebouncing] = useDebounce(async (query: string) => {
if (!query.trim()) return
const results = await searchApi(query)
// Handle results
}, 300)
function handleInput(event: Event) {
const value = (event.target as HTMLInputElement).value
searchQuery.value = value
debouncedSearch(value)
}
</script>
<template>
<Input
:model-value="searchQuery"
placeholder="Search..."
:loading="isDebouncing"
@input="handleInput"
/>
</template>4.2 Button Click Throttling
<script setup lang="ts">
import { useThrottle } from 'brutx-ui-vue'
const [throttledSubmit] = useThrottle(async () => {
await submitForm()
}, 1000)
</script>
<template>
<Button @click="throttledSubmit">Submit (Throttle 1s)</Button>
</template>4.3 High Frequency Scroll Listeners
<script setup lang="ts">
import { useThrottle, useEventListener } from 'brutx-ui-vue'
const [throttledScrollHandler] = useThrottle((event: Event) => {
const target = event.target as HTMLElement
const scrollTop = target.scrollTop
// Process scroll values
}, 100)
useEventListener(window, 'scroll', throttledScrollHandler)
</script>5. Virtual Scroll Configurations
5.1 Basic Usage
<template>
<VirtualScroll
:items="largeList"
:item-height="64"
:buffer="10"
class="h-[500px] overflow-auto"
>
<template #default="{ item, index }">
<div class="flex items-center gap-3 px-4 py-3 border-b border-brutal-border">
<Avatar>
<AvatarImage :src="item.avatar" />
<AvatarFallback>{{ item.name[0] }}</AvatarFallback>
</Avatar>
<div>
<p class="font-medium">{{ item.name }}</p>
<p class="text-sm text-brutal-muted-foreground">{{ item.email }}</p>
</div>
</div>
</template>
</VirtualScroll>
</template>5.2 Infinite Loading
<script setup lang="ts">
import { ref } from 'vue'
import { VirtualScroll, InfiniteScroll } from 'brutx-ui-vue'
const items = ref<Item[]>([])
const hasMore = ref(true)
const isLoading = ref(false)
async function loadMore() {
if (isLoading.value || !hasMore.value) return
isLoading.value = true
const newItems = await fetchItems(items.value.length)
items.value = [...items.value, ...newItems]
hasMore.value = newItems.length > 0
isLoading.value = false
}
</script>
<template>
<VirtualScroll
:items="items"
:item-height="48"
class="h-[600px]"
>
<template #default="{ item }">
<div class="px-4 py-2">{{ item.name }}</div>
</template>
</VirtualScroll>
<InfiniteScroll
:loading="isLoading"
:has-more="hasMore"
@load-more="loadMore"
/>
</template>5.3 Dynamic Heights (Estimated Height)
For layouts with variable row heights, use the estimated-item-height property:
<template>
<VirtualScroll
:items="items"
:estimated-item-height="80"
:buffer="5"
class="h-[600px]"
>
<template #default="{ item }">
<div class="px-4 py-3">
<!-- Variable content height -->
<p>{{ item.content }}</p>
<p v-if="item.expanded">{{ item.detail }}</p>
</div>
</template>
</VirtualScroll>
</template>6. Performance Audit Checklist
6.1 Using Vue Devtools
During development, monitor rendering cycles and compute timings:
- Open Vue Devtools in browser.
- Navigate to the Performance Tab.
- Record rendering trace segments.
- Track down and optimize component redraw bottlenecks.
6.2 Render Optimization Best Practices
- [ ] Large list datasets use
shallowRefconfigurations. - [ ] Large lists are wrapped inside
VirtualScrollcomponents. - [ ] Live input searches use debounce thresholds (minimum 300ms).
- [ ] Layout scroll events use throttle rates (minimum 100ms).
- [ ] Offscreen modals and dialog triggers use lazy resolution bundles.
- [ ] Complex computations in template bindings are computed values.
- [ ] Static components are marked with
v-once. - [ ] Repetitive lists utilize
v-memocaches.
<template>
<!-- Cache list node updates with v-memo -->
<div
v-for="item in list"
:key="item.id"
v-memo="[item.id, item.selected]"
>
{{ item.name }}
</div>
</template>