Project Organization Best Practices

+15 Mana ✨

As your Vue application grows, good organization becomes essential. Let's establish patterns that scale from small projects to large applications.

src/
├── assets/              # Static assets (images, fonts)
│   ├── images/
│   └── styles/
│       ├── main.css
│       └── variables.css
│
├── components/          # Reusable components
│   ├── common/          # Generic, app-wide components
│   │   ├── BaseButton.vue
│   │   ├── BaseInput.vue
│   │   ├── BaseModal.vue
│   │   └── BaseCard.vue
│   ├── layout/          # Layout components
│   │   ├── AppHeader.vue
│   │   ├── AppFooter.vue
│   │   └── AppSidebar.vue
│   └── [feature]/       # Feature-specific components
│       ├── TodoItem.vue
│       └── TodoList.vue
│
├── composables/         # Reusable composition functions
│   ├── useAuth.ts
│   ├── useFetch.ts
│   └── useLocalStorage.ts
│
├── views/               # Page-level components
│   ├── HomeView.vue
│   ├── AboutView.vue
│   └── NotFoundView.vue
│
├── types/               # TypeScript type definitions
│   └── index.ts
│
├── utils/               # Utility functions
│   ├── formatters.ts
│   └── validators.ts
│
├── App.vue              # Root component
└── main.ts              # Application entry point

Component Naming Patterns

Base Components

Prefix generic components with Base, App, or V:

BaseButton.vue      # Generic button
BaseInput.vue       # Generic input
BaseCard.vue        # Generic card
AppHeader.vue       # App-specific header

Single-Instance Components

Prefix components used only once with The:

TheNavbar.vue       # Only one navbar
TheSidebar.vue      # Only one sidebar
TheFooter.vue       # Only one footer

Tightly Coupled Components

Name child components with parent as prefix:

TodoList.vue
TodoListItem.vue
TodoListItemButton.vue

SearchForm.vue
SearchFormInput.vue
SearchFormButton.vue

File Organization Tips

components/
└── todo/
    ├── TodoList.vue
    ├── TodoItem.vue
    ├── TodoForm.vue
    └── types.ts          # Types specific to this feature

Co-locate Tests

components/
└── todo/
    ├── TodoList.vue
    ├── TodoList.spec.ts  # Test next to component
    └── ...

Creating Type Definitions

typescript
// src/types/index.ts
export type User = {
  id: number
  name: string
  email: string
  avatar?: string
}

export type Todo = {
  id: number
  text: string
  done: boolean
  createdAt: Date
  userId: number
}

export type ApiResponse<T> = {
  data: T
  message: string
  success: boolean
}

Creating Utility Functions

typescript
// src/utils/formatters.ts
export function formatDate(date: Date): string {
  return new Intl.DateTimeFormat('en-US', {
    year: 'numeric',
    month: 'long',
    day: 'numeric'
  }).format(date)
}

export function formatCurrency(amount: number): string {
  return new Intl.NumberFormat('en-US', {
    style: 'currency',
    currency: 'USD'
  }).format(amount)
}

export function truncate(text: string, length: number): string {
  if (text.length <= length) return text
  return text.slice(0, length) + '...'
}

Using Utilities in Components

vue
<script setup lang="ts">
import { formatDate, formatCurrency } from '@/utils/formatters'
import type { Product } from '@/types'

const props = defineProps<{
  product: Product
}>()
</script>

<template>
  <div class="product">
    <h3>{{ product.name }}</h3>
    <p>{{ formatCurrency(product.price) }}</p>
    <small>Added {{ formatDate(product.createdAt) }}</small>
  </div>
</template>

Path Aliases

Configure path aliases in vite.config.ts for cleaner imports:

typescript
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { fileURLToPath, URL } from 'node:url'

export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  }
})

Now you can import like this:

typescript
// Instead of: import Button from '../../../components/common/BaseButton.vue'
import BaseButton from '@/components/common/BaseButton.vue'
import { formatDate } from '@/utils/formatters'
import type { User } from '@/types'
✓ Completed