Drop your stack into the estimator. We'll tell you exactly which deep-dive to read next.
Your app is in good shape. Focus on establishing patterns before it grows.
You passed `user` as a prop from App.vue → Layout → Page → Section → Card → Avatar. Now the Avatar needs `user.permissions`. Everyone in between touches props they don't use.
| 1 | // App.vue |
| 2 | |
| 3 | |
| 4 | |
| 5 | |
| 6 | // Layout.vue — receives user, passes it down |
| 7 | defineProps<{ user: User }>()← 🐛 |
| 8 | |
| 9 | // Page.vue — same thing |
| 10 | defineProps<{ user: User }>()← 🐛 |
| 11 | |
| 12 | // Avatar.vue — finally uses it |
| 13 | const props = defineProps<{ user: User }>() |
| 14 | const canEdit = props.user.permissions.edit← 🐛 |
Define the injection key once at the root. Any descendant — at any depth — injects it directly. Zero prop tunneling. Full TypeScript safety.
| 1 | // injectionKeys.ts |
| 2 | import { InjectionKey } from 'vue' |
| 3 | export const UserKey: InjectionKey |
| 4 | Symbol('user') |
| 5 | |
| 6 | // App.vue — provide once at root |
| 7 | import { provide } from 'vue' |
| 8 | provide(UserKey, user) |
| 9 | |
| 10 | // Avatar.vue — inject anywhere |
| 11 | import { inject } from 'vue' |
| 12 | const user = inject(UserKey)! |
| 13 | const canEdit = user.permissions.edit |
A `watch` on `filters` calls `fetchData()`, which updates `results`, which triggers a side effect that mutates `filters`. Stack overflow at 11:43pm on a Friday deploy.
| 1 | // ❌ circular watch chain |
| 2 | const filters = reactive({ sort: 'asc', page: 1 }) |
| 3 | const results = ref([]) |
| 4 | |
| 5 | watch(filters, async () => {← 🐛 |
| 6 | results.value = await fetchData(filters)← 🐛 |
| 7 | })← 🐛 |
| 8 | |
| 9 | // somewhere else in the component... |
| 10 | watch(results, (data) => {← 🐛 |
| 11 | if (data.length === 0) {← 🐛 |
| 12 | filters.page = 1 // ← triggers first watch again← 🐛 |
| 13 | } |
| 14 | }) |
Replace the circular watch chain with a computed that derives state deterministically. One direction. No cycles. The Vue reactivity graph can't loop on a DAG.
| 1 | // ✓ unidirectional data flow |
| 2 | const filters = reactive({ sort: 'asc', page: 1 }) |
| 3 | |
| 4 | const queryKey = computed(() => |
| 5 | `${filters.sort}-${filters.page}` |
| 6 | ) |
| 7 | |
| 8 | const { data: results } = useQuery({ |
| 9 | queryKey: [queryKey], |
| 10 | queryFn: () => fetchData(filters), |
| 11 | }) |
| 12 | |
| 13 | // page resets live in the action, not a watcher |
| 14 | function setSort(s) { filters.sort = s; filters.page = 1 } |
User auth state lives in App.vue. Cart state is in a Vuex module nobody understands. Form state is local. Every refactor touches 8 files. The tech lead wants to rewrite in React.
| 1 | // state in 4 different places |
| 2 | const user = ref(null) // App.vue← 🐛 |
| 3 | |
| 4 | // store/cart.js (Vuex, no types) |
| 5 | state: () => ({ items: [], total: 0 }),← 🐛 |
| 6 | mutations: { ADD_ITEM(state, item) {← 🐛 |
| 7 | state.items.push(item) // no validation← 🐛 |
| 8 | }} |
| 9 | |
| 10 | // ProductCard.vue — needs both |
| 11 | const store = useStore()← 🐛 |
| 12 | const canBuy = computed(() =>← 🐛 |
| 13 | store.state.cart.total < user.value?.limit)← 🐛 |
Encapsulate each domain in a composable that owns its state, actions, and derived values. Pinia for cross-component persistence. Everything else stays local. Rewrite cancelled.
| 1 | // stores/cart.ts — typed Pinia store |
| 2 | export const useCartStore = defineStore('cart', () => { |
| 3 | const items = ref |
| 4 | const total = computed(() => |
| 5 | items.value.reduce((s, i) => s + i.price, 0)) |
| 6 | function addItem(item: CartItem) { |
| 7 | if (!item.inStock) return // validation here |
| 8 | items.value.push(item) |
| 9 | } |
| 10 | return { items, total, addItem } |
| 11 | }) |
| 12 | |
| 13 | // ProductCard.vue — clean |
| 14 | const cart = useCartStore() |
"I'd been staring at the same watchEffect loop for 3 hours. Found Reactive at 11:47pm, read the deep dive, fixed it by midnight. The code examples are exactly how I write code — not some sanitized tutorial version."
"We were 6 weeks into a Vuex → Pinia migration and completely lost. The architecture article gave us a migration playbook we actually used. Saved at least two weeks of argument in Slack."
"I picked Vue because Laravel Breeze told me to. A year later I had no idea what I was doing with composables. Reactive is the resource I wish existed when I started."
"The SSR hydration article is the most technically honest piece of writing I've read about Nuxt. No hand-waving, just the actual edge cases with reproduction steps."
Not on a schedule. Not a newsletter. A feed that updates when a real Vue problem gets solved and documented. No fluff, no announcements.
No marketing. No re-engagement campaigns. Just Vue.