Common Development Issues & Solutions
Common Development Issues & Solutions
Section titled “Common Development Issues & Solutions”A comprehensive troubleshooting guide to help you quickly resolve common issues encountered during secondary development of the Vue admin template. This guide covers environment setup, component integration, styling conflicts, build processes, and deployment challenges.
🛠️ Environment Configuration Issues
Section titled “🛠️ Environment Configuration Issues”Node.js Version Compatibility
Section titled “Node.js Version Compatibility”Problem: Getting errors like “Unsupported Node.js version” or compatibility issues with dependencies.
# Check current Node.js versionnode --version
# Expected versions for this template:# Node.js: >= 20.19.0
# Install/switch to correct Node.js version using nvmnvm install 20nvm use 20
# Or using voltavolta install node@20
# Verify installationnode --version # Should output v20.19.x or higher
Solutions:
Use Node Version Manager (nvm):
Terminal window # Install nvm (if not already installed)curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash# Install and use Node.js 20+nvm install 20nvm use 20nvm alias default 20Use Volta (recommended):
Terminal window # Install voltacurl https://get.volta.sh | bash# Pin Node.js version for the projectvolta pin node@20Update package.json engines:
{"engines": {"node": ">=20.19.0"}}
Problem: Dependency conflicts, lock file mismatches, or installation failures.
# Clear all caches and reinstallrm -rf node_modulesrm package-lock.json # or pnpm-lock.yamlnpm cache clean --force # or pnpm store prune
# Fresh installationnpm install # or pnpm install
# If using multiple package managers, stick to oneecho "package-manager=pnpm" > .npmrc
# For persistent issues, try:npm install --legacy-peer-deps# orpnpm install --no-frozen-lockfile
Common Solutions:
Dependency Version Conflicts:
Terminal window # Check for version conflictsnpm ls# orpnpm list# Resolve peer dependency warningsnpm install --save-dev @types/nodepnpm add -D @types/nodeLock File Issues:
Terminal window # If switching between npm and pnpmrm package-lock.json pnpm-lock.yamlpnpm install# Commit the correct lock filegit add pnpm-lock.yamlgit commit -m "fix: update lock file for pnpm"
Problem: Environment variables not loading or incorrect API endpoints.
# .env.local (for local development)# Add your environment-specific variables here# All variables must be prefixed with VITE_ to be accessible in the browser
# Example API configuration# VITE_API_BASE_URL=http://localhost:3000/api
# Example app configuration# VITE_APP_NAME=Vue Admin Template# VITE_APP_VERSION=1.0.0
# .env.production (for production builds)# VITE_API_BASE_URL=https://api.yourdomain.com# VITE_APP_NAME=Vue Admin Template
# Note: Create these files as needed for your specific use case# The template works without environment variables by default
Debug Environment Variables:
export function debugEnvironment() { console.group('🔧 Environment Debug Info') console.log('Mode:', import.meta.env.MODE) console.log('Base URL:', import.meta.env.BASE_URL) console.log('Production:', import.meta.env.PROD) console.log('Development:', import.meta.env.DEV)
// Log any custom VITE_ variables you've added // console.log('API Base URL:', import.meta.env.VITE_API_BASE_URL) // console.log('App Name:', import.meta.env.VITE_APP_NAME)
console.groupEnd()}
// Call in main.ts during developmentif (import.meta.env.DEV) { debugEnvironment()}
🧩 Component Usage Problems
Section titled “🧩 Component Usage Problems”HiTable Integration Issues
Section titled “HiTable Integration Issues”Problem: Table not displaying data or showing “No data available” despite having data.
// ❌ Common mistakesconst tableData = ref() // undefinedconst tableData = ref({}) // object instead of arrayconst tableData = ref([{ id: 1, user_name: 'John' }]) // snake_case keys
// ✅ Correct data formatconst tableData = ref<User[]>([])
// ✅ Column definitions must match data keysconst columns: ColumnDef<User>[] = [ { accessorKey: 'name', header: 'Name' }, // matches data.name { accessorKey: 'email', header: 'Email' }, // matches data.email // ❌ { accessorKey: 'user_name', header: 'Name' } // key doesn't exist]
// ✅ Handle async data properlyconst { data: users, loading } = useAsyncData('users', () => fetchUsers())const tableData = computed(() => users.value || [])
Debugging Steps:
// Add debugging to understand data structurewatch(() => tableData.value, (newData) => { console.log('Table data:', newData) console.log('Data type:', typeof newData) console.log('Is array:', Array.isArray(newData)) console.log('Length:', newData?.length) if (newData?.[0]) { console.log('First item keys:', Object.keys(newData[0])) }}, { immediate: true })
Problem: Columns not rendering correctly, sorting not working, or custom cells not displaying.
// ❌ Common column configuration mistakesconst columns = [ { accessorKey: 'name', header: 'Name', sortable: true }, // ❌ sortable should be enableSorting { key: 'email', title: 'Email' }, // ❌ wrong property names { accessorKey: 'status', header: 'Status', cell: 'BadgeCell' } // ❌ cell should be function]
// ✅ Correct column configurationimport { h } from 'vue'import { Badge } from '@/components/ui/badge'import { Button } from '@/components/ui/button'
const columns: ColumnDef<User>[] = [ { accessorKey: 'name', header: 'Name', enableSorting: true, meta: { displayName: 'Full Name' } // For column visibility }, { accessorKey: 'email', header: 'Email Address', enableSorting: true, cell: ({ getValue }) => { const email = getValue() as string return h('a', { href: `mailto:\${email}`, class: 'text-blue-600 hover:underline' }, email) } }, { accessorKey: 'status', header: 'Status', enableSorting: false, cell: ({ row }) => { const status = row.getValue('status') as string return h(Badge, { variant: status === 'active' ? 'default' : 'secondary' }, () => status) } }, { id: 'actions', header: 'Actions', enableSorting: false, enableHiding: false, cell: ({ row }) => { return h('div', { class: 'flex gap-2' }, [ h(Button, { size: 'sm', variant: 'outline', onClick: () => editUser(row.original.id) }, () => 'Edit'), h(Button, { size: 'sm', variant: 'destructive', onClick: () => deleteUser(row.original.id) }, () => 'Delete') ]) } }]
Problem: Table is slow with large datasets or frequent re-renders.
// ❌ Performance anti-patternsconst columns = computed(() => [ // ❌ columns recreated on every render { accessorKey: 'name', header: 'Name' }])
const data = computed(() => rawData.value.map(item => ({ // ❌ data transformed on every render ...item, displayName: processName(item.name) // expensive operation})))
// ✅ Optimized patternsconst columns = shallowRef<ColumnDef<User>[]>([ // ✅ stable reference { accessorKey: 'name', header: 'Name', cell: ({ getValue }) => { const name = getValue() as string // ✅ lightweight operations only return h('span', { class: 'font-medium' }, name) } }])
// ✅ Pre-process data when it changes, not on every renderconst processedData = computed(() => { return rawData.value.map(item => ({ ...item, searchableText: `\${item.name} \${item.email}`.toLowerCase(), // pre-compute for search displayDate: formatDate(item.createdAt) // pre-compute expensive formatting }))})
// ✅ Debounce search to avoid excessive filteringconst searchTerm = ref('')const debouncedSearch = useDebounceFn((value: string) => { tableRef.value?.table.setGlobalFilter(value)}, 300)
watch(searchTerm, debouncedSearch)
// ✅ Virtual scrolling for very large datasetsconst virtualizedConfig = computed(() => ({ ...baseConfig, virtualization: { enabled: processedData.value.length > 1000, itemHeight: 48 }}))
HiForm Validation Problems
Section titled “HiForm Validation Problems”Problem: Form validation not working or showing incorrect error messages.
// ❌ Common Zod schema mistakesconst schema = z.object({ email: z.string(), // ❌ no email validation age: z.string().min(18), // ❌ string instead of number role: z.string().oneOf(['admin', 'user']), // ❌ should use enum confirmPassword: z.string() // ❌ no password confirmation logic})
// ✅ Correct Zod schema with proper validationconst schema = z.object({ email: z.string() .min(1, 'Email is required') .email('Please enter a valid email address'),
age: z.preprocess( (val) => val === '' ? undefined : Number(val), // Handle empty strings z.number() .min(18, 'Must be at least 18 years old') .max(120, 'Must be less than 120 years old') ),
role: z.enum(['admin', 'user', 'moderator'], { errorMap: () => ({ message: 'Please select a valid role' }) }),
password: z.string() .min(8, 'Password must be at least 8 characters') .regex(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/, 'Password must contain uppercase, lowercase, and number'),
confirmPassword: z.string()}).refine((data) => data.password === data.confirmPassword, { message: "Passwords don't match", path: ["confirmPassword"] // Show error on confirmPassword field})
// ✅ Optional fields with defaultsconst optionalSchema = z.object({ name: z.string().min(1), bio: z.string().max(500).optional().default(''), newsletter: z.boolean().default(false), preferences: z.object({ theme: z.enum(['light', 'dark']).default('light'), language: z.string().default('en') }).optional().default({})})
Problem: Form fields not rendering or behaving incorrectly.
// ❌ Common field configuration mistakesconst fields = [ { name: 'email', type: 'email' }, // ❌ type should be 'input' with inputType: 'email' { name: 'role', type: 'select' }, // ❌ missing options { name: 'active', type: 'boolean' }, // ❌ type should be 'checkbox' or 'switch' { name: 'skills', type: 'multiselect' } // ❌ type doesn't exist]
// ✅ Correct field configurationsconst fields: FormFieldConfig[] = [ { type: 'input', name: 'email', label: 'Email Address', inputType: 'email', // ✅ specify input type placeholder: 'Enter your email', required: true }, { type: 'select', name: 'role', label: 'User Role', placeholder: 'Select a role', options: [ // ✅ required for select fields { label: 'Administrator', value: 'admin' }, { label: 'Regular User', value: 'user' }, { label: 'Moderator', value: 'moderator' } ], required: true }, { type: 'switch', // ✅ or 'checkbox' for boolean values name: 'active', label: 'Active Status', text: 'Enable user account' }, { type: 'combobox', // ✅ use combobox for searchable multi-select name: 'skills', label: 'Technical Skills', placeholder: 'Select skills', searchPlaceholder: 'Search skills...', options: [ { label: 'Vue.js', value: 'vue' }, { label: 'React', value: 'react' }, { label: 'TypeScript', value: 'typescript' } ] }, { type: 'input', name: 'salary', label: 'Salary Range', inputType: 'number', min: 0, max: 1000000, step: 1000 }]
// ✅ Conditional field exampleconst conditionalField: FormFieldConfig = { type: 'input', name: 'companyName', label: 'Company Name', placeholder: 'Enter company name', condition: { field: 'hasJob', // Field to watch value: true, // Value to match operator: 'eq' // Comparison operator }}
Problem: Form state not updating or validation timing issues.
// ❌ Common state management mistakesconst formData = ref({}) // ❌ not reactive to field changesconst handleSubmit = (data) => { // ❌ not using TypeScript console.log(data)}
// ✅ Proper form state managementinterface UserFormData { name: string email: string role: 'admin' | 'user' active: boolean}
const formRef = ref<InstanceType<typeof HiForm>>()
// ✅ Handle form submission with proper typingconst handleSubmit = async (data: UserFormData) => { try { isSubmitting.value = true
// Validate data one more time (optional) const validatedData = schema.parse(data)
// Submit to API await userService.createUser(validatedData)
// Show success message toast.success('User created successfully!')
// Reset form or redirect formRef.value?.reset() // or router.push('/users')
} catch (error) { if (error instanceof z.ZodError) { // Handle validation errors toast.error('Please check your input and try again') } else { // Handle API errors console.error('Submission error:', error) toast.error('Failed to create user. Please try again.') } } finally { isSubmitting.value = false }}
// ✅ Watch form changes for auto-save or validationconst handleFormChange = useDebounceFn((data: Partial<UserFormData>) => { // Auto-save draft localStorage.setItem('user-form-draft', JSON.stringify(data))
// Real-time validation feedback validatePartialData(data)}, 500)
// ✅ Programmatic form controlconst fillTestData = () => { formRef.value?.setValues({ name: 'John Doe', role: 'user', active: true })}
const validateForm = async () => { const isValid = await formRef.value?.validate() if (isValid) { toast.success('Form is valid!') } else { toast.error('Please fix the errors before submitting') }}
🎨 Styling Conflicts & Solutions
Section titled “🎨 Styling Conflicts & Solutions”CSS Class Conflicts
Section titled “CSS Class Conflicts”Problem: Custom styles being overridden by Tailwind or component styles not applying.
<!-- ❌ Common Tailwind conflicts --><HiTable class="bg-blue-500" /> <!-- May be overridden by component styles --><div class="p-4 p-6"> <!-- Last class wins, p-6 will override p-4 -->
<!-- ✅ Solutions for Tailwind conflicts -->
<!-- Method 1: Use important modifier --><HiTable class="!bg-blue-500" /> <!-- Forces the style -->
<!-- Method 2: Use CSS-in-JS for complex styling --><HiTable :style="{ backgroundColor: 'rgb(59 130 246)' }" />
<!-- Method 3: Use component-specific classes --><HiTable class="custom-table-blue" />
<!-- Method 4: Use CSS custom properties --><HiTable class="custom-table" :style="{ '--table-bg': '#3b82f6' }"/>
CSS Solutions:
/* Method 1: Higher specificity */.custom-table-blue.hi-table { @apply bg-blue-500;}
/* Method 2: CSS custom properties */.custom-table { background-color: var(--table-bg, theme('colors.white'));}
/* Method 3: Component-specific overrides */.hi-table.custom-styling { @apply bg-white border border-gray-200 rounded-lg shadow-sm;}
.hi-table.custom-styling th { @apply bg-gray-50 font-semibold text-gray-900;}
.hi-table.custom-styling td { @apply border-t border-gray-100;}
/* Method 4: Dark mode considerations */.dark .hi-table.custom-styling { @apply bg-gray-800 border-gray-700;}
.dark .hi-table.custom-styling th { @apply bg-gray-700 text-gray-100;}
.dark .hi-table.custom-styling td { @apply border-gray-600;}
Problem: Styles leaking between components or global styles affecting components unexpectedly.
<!-- ❌ Global styles affecting components --><style>.table { /* Too generic, affects all tables */ border: 1px solid red;}</style>
<!-- ✅ Proper style isolation -->
<!-- Method 1: Scoped styles --><template> <div class="user-management"> <HiTable class="custom-user-table" /> </div></template>
<style scoped>.user-management { @apply p-6 bg-white rounded-lg;}
.custom-user-table { @apply border border-gray-200;}
/* Scoped deep selectors for child components */.custom-user-table :deep(.table-header) { @apply bg-blue-50;}
.custom-user-table :deep(.table-row:hover) { @apply bg-blue-25;}</style>
<!-- Method 2: CSS Modules --><template> <div :class="$style.container"> <HiTable :class="$style.table" /> </div></template>
<style module>.container { @apply p-6 bg-white rounded-lg shadow-sm;}
.table { @apply border border-gray-200;
/* Module-scoped custom properties */ --table-hover-color: theme('colors.blue.50');}
.table :global(.table-row:hover) { background-color: var(--table-hover-color);}</style>
<!-- Method 3: BEM methodology for global styles --><template> <div class="user-management"> <HiTable class="user-management__table" /> </div></template>
<style>/* Global styles using BEM */.user-management { @apply p-6 bg-white rounded-lg;}
.user-management__table { @apply border border-gray-200;}
.user-management__table .table-header { @apply bg-blue-50 font-semibold;}
.user-management__table--compact { @apply text-sm;}
.user-management__table--compact .table-cell { @apply py-2;}</style>
Problem: Custom components not respecting the global theme or dark mode.
<!-- ❌ Hard-coded colors that don't respect theme --><div style="background: white; color: black;"> Content</div>
<!-- ✅ Theme-aware styling -->
<template> <div class="theme-aware-component"> <HiTable :class="tableClasses" :config="themeAwareConfig" /> </div></template>
<script setup lang="ts">import { useTheme } from '@/shared/composables/useTheme'
const { isDark, theme } = useTheme()
// ✅ Computed classes based on themeconst tableClasses = computed(() => ({ 'table-light': !isDark.value, 'table-dark': isDark.value, 'table-high-contrast': theme.value === 'high-contrast'}))
// ✅ Theme-aware configurationconst themeAwareConfig = computed(() => ({ styling: { containerClass: isDark.value ? 'bg-gray-800 border-gray-700' : 'bg-white border-gray-200', headerClass: isDark.value ? 'bg-gray-700 text-gray-100' : 'bg-gray-50 text-gray-900', rowClass: isDark.value ? 'hover:bg-gray-700 border-gray-600' : 'hover:bg-gray-50 border-gray-100' }}))</script>
<style scoped>.theme-aware-component { /* Use CSS custom properties that respect theme */ background-color: var(--color-background); color: var(--color-text); border: 1px solid var(--color-border);}
/* Theme-specific overrides */.table-light { --table-bg: theme('colors.white'); --table-text: theme('colors.gray.900'); --table-border: theme('colors.gray.200');}
.table-dark { --table-bg: theme('colors.gray.800'); --table-text: theme('colors.gray.100'); --table-border: theme('colors.gray.700');}
.table-high-contrast { --table-bg: theme('colors.black'); --table-text: theme('colors.white'); --table-border: theme('colors.white');}
/* Apply theme variables */:deep(.hi-table) { background-color: var(--table-bg); color: var(--table-text); border-color: var(--table-border);}</style>
🔨 Build & Deployment Issues
Section titled “🔨 Build & Deployment Issues”Build Errors
Section titled “Build Errors”Problem: Build failing due to TypeScript errors or type checking issues.
// ❌ Common TypeScript errors and fixes
// Error: Property 'data' does not exist on type 'never'const tableData = ref([]) // ❌ TypeScript can't infer typeconst tableData = ref<User[]>([]) // ✅ Explicit type annotation
// Error: Type 'string' is not assignable to type 'never'const formData = reactive({}) // ❌ Empty object has no propertiesconst formData = reactive<UserFormData>({ // ✅ Type the reactive object name: '', email: '', role: 'user'})
// Error: Cannot find module or its corresponding type declarationsimport { SomeComponent } from './components' // ❌ Missing file extension or indeximport { SomeComponent } from './components/SomeComponent.vue' // ✅ Explicit pathimport { SomeComponent } from './components/index' // ✅ Using index file
// Error: 'ref' is declared but its value is never readconst tableRef = ref() // ❌ Unused refconst tableRef = ref<InstanceType<typeof HiTable>>() // ✅ Typed and used
// ✅ Type-safe component refsconst formRef = ref<{ validate: () => Promise<boolean> reset: () => void setValues: (values: any) => void}>()
// ✅ Type-safe event handlersconst handleSubmit = (data: UserFormData) => { // ✅ Typed parameter console.log(data.name) // TypeScript knows about 'name' property}
// ✅ Type-safe API responsesinterface ApiResponse<T> { data: T message: string success: boolean}
const fetchUsers = async (): Promise<ApiResponse<User[]>> => { const response = await fetch('/api/users') return response.json()}
Common TypeScript Configuration Issues:
// tsconfig.json - recommended configuration{ "compilerOptions": { "target": "ES2020", "useDefineForClassFields": true, "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "preserve", "strict": true, "noUnusedLocals": false, // Set to false during development "noUnusedParameters": false, "noFallthroughCasesInSwitch": true, "baseUrl": ".", "paths": { "@/*": ["./src/*"] } }, "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], "references": [{ "path": "./tsconfig.node.json" }]}
Problem: Vite build failures, chunk size warnings, or asset loading issues.
// vite.config.ts - optimized build configurationimport { defineConfig } from 'vite'import vue from '@vitejs/plugin-vue'import { resolve } from 'path'
export default defineConfig({ plugins: [vue()],
// ✅ Path resolution resolve: { alias: { '@': resolve(__dirname, './src'), }, },
// ✅ Build optimization build: { // Increase chunk size warning limit chunkSizeWarningLimit: 1000,
// Manual chunk splitting for better caching rollupOptions: { output: { manualChunks: { // Vendor libraries vue: ['vue'], router: ['vue-router'], pinia: ['pinia'],
// UI libraries ui: ['@headlessui/vue', 'lucide-vue-next'], table: ['@tanstack/vue-table'], form: ['zod'],
// Chart libraries (if using) charts: ['chart.js', 'vue-chartjs'], },
// Asset naming assetFileNames: (assetInfo) => { const info = assetInfo.name.split('.') const ext = info[info.length - 1] if (/png|jpe?g|svg|gif|tiff|bmp|ico/i.test(ext)) { return `assets/images/[name]-[hash].[ext]` } if (/woff2?|eot|ttf|otf/i.test(ext)) { return `assets/fonts/[name]-[hash].[ext]` } return `assets/[name]-[hash].[ext]` }, }, },
// Source maps for production debugging sourcemap: false, // Set to true if needed
// Minification minify: 'terser', terserOptions: { compress: { drop_console: true, // Remove console.log in production drop_debugger: true, }, }, },
// ✅ Development server configuration server: { port: 3000, open: true, cors: true, proxy: { '/api': { target: 'http://localhost:8000', changeOrigin: true, rewrite: (path) => path.replace(/^/api/, ''), }, }, },
// ✅ Dependency optimization optimizeDeps: { include: ['vue', 'vue-router', 'pinia'], exclude: ['@vueuse/core'], // Exclude if causing issues },})
// ✅ Environment-specific configurationconst config = defineConfig(({ command, mode }) => { const env = loadEnv(mode, process.cwd(), '')
return { // Base configuration ...baseConfig,
// Environment-specific overrides define: { __APP_ENV__: JSON.stringify(env.APP_ENV), },
...(mode === 'development' && { // Development-specific config css: { devSourcemap: true, }, }),
...(mode === 'production' && { // Production-specific config build: { ...baseConfig.build, reportCompressedSize: false, // Faster builds }, }), }})
Problem: Application not working correctly after deployment or environment-specific issues.
# .env.productionVITE_API_BASE_URL=https://api.yourdomain.comVITE_APP_NAME=Vue Admin TemplateVITE_BASE_URL=/admin/ # If deploying to subdirectory
# Dockerfile for containerized deploymentFROM node:22-alpine as builder
WORKDIR /appCOPY package*.json ./RUN npm ci --only=production
COPY . .RUN npm run build
FROM nginx:alpineCOPY --from=builder /app/dist /usr/share/nginx/htmlCOPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80CMD ["nginx", "-g", "daemon off;"]
# nginx.conf for SPA routingserver { listen 80; server_name localhost; root /usr/share/nginx/html; index index.html;
# Handle client-side routing location / { try_files $uri $uri/ /index.html; }
# API proxy (if needed) location /api/ { proxy_pass http://api-backend:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }
# Cache static assets location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { expires 1y; add_header Cache-Control "public, immutable"; }}
# GitHub Actions deploymentname: Deploy to Productionon: push: branches: [main]
jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3
- name: Setup Node.js uses: actions/setup-node@v3 with: node-version: '22' cache: 'npm'
- name: Install dependencies run: npm ci
- name: Run tests run: npm test
- name: Build application run: npm run build env: VITE_API_BASE_URL: ${{ secrets.API_BASE_URL }}
- name: Deploy to S3 run: aws s3 sync dist/ s3://${{ secrets.S3_BUCKET }} --delete env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
🚨 Quick Fix Checklist
Section titled “🚨 Quick Fix Checklist”When encountering issues, run through this checklist:
📞 Getting Help
Section titled “📞 Getting Help”If you’re still experiencing issues after trying these solutions:
- Check the GitHub Issues: Search existing issues in the template repository
- Browser DevTools: Use Console, Network, and Vue DevTools for debugging
- Community Support: Join the Vue.js Discord or relevant community forums
- Documentation: Refer to official documentation for Vue 3, Vite, and related libraries