While writing a Vue.js frontend for a client, I decided to explore what ChatGPT’s “understanding” was of Vue project architecture. Large language models are very good at aggregating and summarizing data from a diverse list of sources which makes it a very useful tool for learning; however, always make sure to fact-check responses and request its sources.
The Quick Map
- Views (Pages)
- Route-level screens.
- Components
- Reusable UI building blocks used inside views (and other components).
- Composables
- Reusable logic (not UI) packaged as functions.
- Stores
- Centralized app/state management (Pinia).
- Services
- I/O boundaries (HTTP, storage, SDKs).
- Helpers/Utils
- Tiny, pure functions—no state, no I/O.
Views (a.k.a. Pages)
- What: Route targets (e.g.,
/users/:id). - When: One per URL; coordinates data fetching, state wiring, and layout composition.
- Do: Compose child components, call composables and stores, kick off service calls (often via composables).
- Don’t: Stuff business logic here; keep them thin.
src/views/
UsersView.vue
LearningListView.vue
Components
- What: Reusable, presentational or small feature units (buttons, cards, modals, data-table widgets).
- When: You need UI that can be dropped into multiple places or to split a view into cleaner chunks.
- Do: Accept props, emit events, remain as dumb as possible.
- Don’t: Call remote APIs directly (that’s for services/composables); don’t hold global state.
src/components/
learning/
LearningCard.vue
LearningEditor.vue
ui/
AppButton.vue
AppDialog.vue
Composables (Vue 3 useXxx)
- What: Reusable logic functions (stateful or stateless) that return reactive values/methods.
- When: Share behavior across components (form handling, pagination, infinite scroll, feature logic).
- Do: Encapsulate feature logic and optionally call services; return reactive state and functions.
- Don’t: Become dumping grounds for app-wide state (that’s stores).
// src/composables/useLearning.ts
import { ref } from 'vue'
import { learningService } from '@/services/learning.service'
export function useLearning(service = learningService) {
const items = ref([] as Array<any>)
const loading = ref(false)
const error = ref<string | null>(null)
const fetch = async () => {
loading.value = true
error.value = null
try { items.value = await service.list() }
catch (e: any) { error.value = e?.message ?? 'Failed to load' }
finally { loading.value = false }
}
return { items, loading, error, fetch }
}
Rule of thumb:
- If multiple components need the same logic ⇒ composable.
- If multiple routes/features must share canonical state or cache ⇒ store.
Stores (Pinia)
- What: Centralized, long-lived state with actions/getters (e.g., auth, user profile, cart, feature caches).
- When: State must survive route changes, be shared widely, or be the single source of truth.
- Do: Own domain state; expose actions that call services; persist if needed.
- Don’t: Put transient, purely local UI state here (keep that inside components/composables).
// src/stores/learning.ts
import { defineStore } from 'pinia'
import { learningService } from '@/services/learning.service'
export const useLearningStore = defineStore('learning', {
state: () => ({ items: [] as any[], loading: false, error: null as string | null }),
actions: {
async load() {
this.loading = true
this.error = null
try { this.items = await learningService.list() }
catch (e: any) { this.error = e?.message ?? 'Failed to load' }
finally { this.loading = false }
}
}
})
Services (API/SDK layer)
- What: Thin, testable wrappers around I/O: HTTP calls, localStorage, Graph API, etc.
- When: Anything that crosses the app boundary.
- Do: Return typed DTOs or map to domain models; handle endpoints, headers, and errors here.
- Don’t: Keep UI concerns or global state; avoid importing Vue reactivity.
// src/services/http.ts
import axios from 'axios'
export const http = axios.create({
baseURL: import.meta.env.VITE_BACKEND_URL,
withCredentials: true,
})
// src/services/learning.service.ts
import { http } from './http'
export type Learning = { id: number; title: string }
export type NewLearning = { title: string }
export const learningService = {
list: async () => (await http.get<Learning[]>('/v1/main/profile/learning/1/50')).data,
create: async (payload: NewLearning) => (await http.post('/v1/main/profile/learning', payload)).data,
}
Helpers / Utils
- What: Small, pure functions with no side effects (formatters, sorters, validators).
- When: You need shared math/string/date logic, and it doesn’t need Vue or I/O.
- Do: Keep them tiny and unit-testable.
- Don’t: Reference Vue,
window, or services here.
// src/utils/date.ts
export const formatUtc = (d: string | Date) => new Date(d).toLocaleString()
How to Choose (Cheat Sheet)
| Need | Put it in |
|---|---|
| New URL/screen | View |
| Reusable UI piece | Component |
| Reusable behavior (reactive) | Composable |
| Shared, canonical app/feature state | Store |
| External calls (HTTP/SDK/storage) | Service |
| Pure function (no I/O) | Helper/Util |
Suggested Folder Layout
src/
assets/ # images, fonts
components/ # shared UI
composables/ # useXxx composables (feature or generic)
router/ # routes
services/ # http clients & SDK wrappers
http.ts # axios/fetch instance
stores/ # pinia stores
utils/ # pure functions
views/ # route pages
types/ # global TS types/interfaces
styles/ # global css/scss
App.vue
main.ts
Conventions (Quick Hits)
- Naming:
useXxxfor composables,SomethingServicefor services,useXxxStorefor stores. - Typing: Put request/response typings in
types/or near the feature. - Imports: Components → composables & stores; composables → services/utils; stores → services; services → nothing Vue‑specific.
- Testing:
- utils/services ⇒ unit tests
- composables ⇒ logic tests (with Vue Test Utils)
- components/views ⇒ component tests
Common Pitfalls
- Putting API calls inside components → move to services, call via composables/stores.
- Using stores for ephemeral UI state → keep local or in a composable.
- Bloated views → split into components + composables.
- “God” composables mixing I/O and global state → if state must be canonical, push it into a store.
Next, I asked it where its “opinion” on do’s and dont’s came from. It couldn’t point to a specific repo or codebase and explained it doesn’t have direct access to its training data or private software. The guidance appears to come from common patterns in documentation, community guides, and widely shared examples—not any single project. I also asked it to scan public repos and name known projects; that didn’t change much, and the answers stayed largely the same.
Short answer: it’s not “my” opinion so much as a mash‑up of widely accepted patterns from official docs and community practices (Vue, Pinia, separation of concerns, and front‑end testing norms).
- Keep UI pieces presentational and reusable.
- Make logic shareable and testable (composables).
- Avoid coupling UI to APIs (services).
- Give shared, canonical state a single home (stores).
- Keep pure utilities tiny and side‑effect free (helpers).
Finally I asked it to give me direct links to the references it used to answer my prompt.
References
- Components Basics | Vue.js
- Props | Vue.js
- Getting Started | Vue Router — what
<RouterView>renders - Composables | Vue.js
- Defining a Store | Pinia
- State Management | Vue.js (Pinia recommendation)
- Data Fetching | Vue Router
- Vue Style Guide
- Reusability & Composition (testing composables) | Vue Test Utils
- Testing | Vue.js
What are you thoughts on using AI to summarize data for learning purposes? Do you like the do’s and don’ts section it provided?

