React or Vue? The Stack I Standardize My Teams On (and Why it's Vue)

2026-06-13 · Ben Williamson

React or Vue? The Stack I Standardize My Teams On

This is not an argument that React is bad.

React is good. React is proven. React has a huge hiring pool, a giant ecosystem, and a lot of excellent tooling.

But when I reach for Vue, especially with Quasar, Pinia, TanStack Vue Query, Tailwind, and often PocketBase / Meilisearch behind it, I’m usually choosing a stack that feels easier to teach, easier to standardize, and easier to keep boring.

These days I make that call as a lead architect, for a team and not just for myself, and that changes what “good” means. I optimize less for what I can build alone and more for what the whole team can learn, ship, and keep maintaining after I’ve moved on to the next thing.

My gut feeling is:

The fewer decisions a team has to make before shipping, the faster everyone moves and the easier the codebase is to hand off.

The leadership-ey reason: fewer decisions before shipping

React often feels like a powerful rendering library surrounded by choices.

A React project may need to decide:

  • Which app framework?

  • Which router?

  • Which UI kit?

  • Which state library?

  • Which query/cache library?

  • Which form library?

  • Which table library?

  • Which mobile wrapper?

  • Which project conventions?

Those choices are not bad. They’re pretty normal for many projects and are part of React’s strengths.

But they are still choices and decisions.

With my current Vue stack, the shape is more intuitive I think:

  • Quasar = UI/app/platform framework including Vue components

    • This is a lot like the UI kits like Material UI and Tailwind Plus but super charged

    • Compile to any platform - SPA/PWA/SSR/Capacitor/Cordova/Electron/Browser extensions too

  • Pinia = client/app/domain state

  • Tailwind = Primary styling after Quasar component props

    • Quasar has great built in style/css features but it’s not my style - so I found Tailwind with a prefixer if needed works great to pivot away from the default Quasar aesthetic

Optional and Stack Independent:

  • TanStack Vue Query = server/cache state (TanStack is on every framework I think)

  • PocketBase = lightweight backend/auth/content/config

  • Meilisearch = fast search/index layer

This setup, I strongly believe, gives the team a super low friction default. I’ve set up several projects this way and the velocity is outstanding.

Vue is legible to the layman developer

Vue components often look like what they do.

<template>
  <q-card>
    <q-card-section>
      <div class="text-h6">{{ product.name }}</div>
      <div class="text-subtitle2">{{ product.sku }}</div>
    </q-card-section>

    <q-card-actions align="right">
      <q-btn label="Edit" color="primary" @click="$emit('edit', product)" />
    </q-card-actions>
  </q-card>
</template>

<script setup>
defineProps({
  product: {
    type: Object,
    required: true,
  },
})

defineEmits(['edit'])
</script>

Most people with basic HTML and JavaScript can infer what is happening:

{{ value }} = print this value
@click = handle click event
:prop = bind a dynamic prop
defineProps = data coming in
defineEmits = events going out

React can also be readable, especially with a good team and conventions. But Vue templates feel closer to HTML and closer to the final UI structure.

That matters A LOT when onboarding people into an existing project.

Named slots are a great example

Vue named slots are one of the clearest examples of why Vue ‘feels’ right, and teachable, for me.

<ProductShell>
  <template #title>
    Edit Product
  </template>

  <ProductFields />

  <template #actions="{ save, loading }">
    <q-btn
      label="Save"
      color="primary"
      :loading="loading"
      @click="save"
    />
  </template>
</ProductShell>

This reads almost like instructions:

  • Put this in the title area.

  • Put this in the body.

  • Put this in the actions area.

  • The shell gives the actions slot save/loading.

React can solve the same problem with render props, children functions, or component props. But Vue’s syntax makes component extension points very obvious.

For internal component libraries, admin screens, and shared team UI patterns, that clarity is valuable. For my preferred topologies like app-shell setups, this is nearly essential lest we lose our minds in merge conflicts.

Pinia is boring in the best way

Pinia is easy to explain:

export const useUiStore = defineStore('ui', {
  state: () => ({
    selectedStoreView: 'default',
    drawerOpen: false,
    denseMode: true,
  }),

  actions: {
    setStoreView(code) {
      this.selectedStoreView = code
    },

    toggleDrawer() {
      this.drawerOpen = !this.drawerOpen
    },
  },

  persist: {
    paths: ['selectedStoreView', 'denseMode'],
  },
})

The mental model is simple:

  1. state = data

  2. getters = computed values

  3. actions = methods (aka queries, mutations)

  4. persist = remember this (Pinia persisted state plugin is this)

And inside actions, you can usually just change the thing:

this.drawerOpen = !this.drawerOpen

React’s state model is powerful, but it often introduces more concepts earlier: immutability, reducers, providers, context, custom hooks, or third-party state libraries.

Not impossible. Just more ceremony.

Pinia should not become the cache layer

This is where TanStack Vue Query makes the Vue stack stronger.

Pinia = client/app/domain state

Vue Query = remote/server/cache state

Pinia should remember things like:

  • selected store view

  • open drawer

  • current UI mode

  • locally selected filters

  • user preferences

  • draft-ish app state

Vue Query should handle things like:

  • GraphQL results

  • API responses

  • stale time

  • cache invalidation

  • background refetching

  • deduped requests

Example:

export function useProductQuery(sku) {
  const ui = useUiStore()

  return useQuery({
    queryKey: computed(() => [
      'product',
      sku.value,
      ui.selectedStoreView,
    ]),

    queryFn: () => fetchProductBySku({
      sku: sku.value,
      storeView: ui.selectedStoreView,
    }),

    enabled: computed(() => !!sku.value),
    staleTime: 1000 * 60 * 5,
  })
}

That keeps Pinia from becoming this:

state: () => ({
  products: [],
  productsFetchedAt: null,
  productsLoading: false,
  productErrors: {},
  categoryProducts: {},
  categoryProductsFetchedAt: {},
  ttlByEndpoint: {},
})

That’s not really app state anymore. That is a homemade query cache. I totally don’t do this all the time.

So the better rule is:

Pinia remembers what the user or app chose. Vue Query handles what the server said.

Quasar is a force multiplier

Quasar is probably the biggest practical reason I like Vue.

It gives me a standard way to build (That is not uncommon in other frameworks either):

  1. layouts

  2. drawers

  3. menus

  4. dialogs

  5. forms

  6. tables

  7. notifications

  8. loading states

  9. dark mode support throughout

  10. PWA apps

  11. Capacitor apps

  12. SSR-capable apps

  13. desktop-style admin interfaces (Key combo menus and such are a breeze)

Instead of picking a separate package for every UI problem, the team can learn the Quasar way once and reuse it everywhere.

<q-table
  :rows="rows"
  :columns="columns"
  row-key="sku"
  :loading="loading"
>
  <template #body-cell-actions="{ row }">
    <q-td>
      <q-btn
        dense
        flat
        icon="edit"
        @click="editRow(row)"
      />
    </q-td>
  </template>
</q-table>

That is a big advantage for:

  • admin apps

  • internal tools

  • dashboards

  • ecommerce widgets

  • Magento (Or your platform)-adjacent SPAs

  • progressively mounted frontend components

  • mobile-ish PWA tools

Is there a React equivalent to Quasar?

Kind of, but not perfectly.

The React world has equivalents for pieces of Quasar:

  • UI component kit:

    • MUI, Mantine, Ant Design

  • Mobile/PWA/Capacitor path:

    • Ionic React + Capacitor

  • Admin/internal tools:

    • React Admin, Refine

  • Full-stack web framework:

    • Next.js

  • Native mobile:

    • Expo / React Native

A React version of a Quasar-ish stack might look like:

  • Vite or Next.js (Quasar uses Vite too)

  • + Mantine or MUI

  • + TanStack Query

  • + Zustand or Redux Toolkit

  • + React Hook Form

  • + Capacitor if needed

For mobile-first apps:

  • Ionic React

  • + Capacitor

  • + TanStack Query

  • + Zustand

For CRUD/internal business apps:

  • React Admin or Refine

  • + MUI/Mantine/shadcn

  • + TanStack Query

So React absolutely can do the same jobs.

The difference is that React usually assembles the stack from more separate decisions.

Vue/Quasar feels more integrated to me.

Quasar gives me the app shell, UI components, layout conventions, and platform targets.

React gives me excellent ingredients, but I still have to choose the recipe.

PocketBase and Meilisearch make the stack fun

A lot of my projects do not need to become enterprise platforms on day one.

They need to be useful, fast, understandable, and easy to change.

That is where this pattern feels nice:

  • PocketBase = auth, data, content/config, admin UI

  • Meilisearch = fast search, filters, projected read models, vectors, dynamic schemas (It’s Algolia)

  • Vue Query = fetch/cache remote state (TTL boilerplate be gone)

  • Pinia = local app state

  • Quasar = UI shell and Vue component model

A common architecture can be:

  • PocketBase stores source/config/content.

  • Hooks or workflows project search-friendly records.

  • Meilisearch serves fast indexed reads.

  • Vue Query fetches and caches remote data.

  • Pinia tracks local app decisions.

  • Quasar renders the interface.

That stack is not trying to impress anyone. It is trying to stay understandable while agile.

So then, why Vue

A React developer could fairly say:

React is easy too, once you know the patterns.

And they’re right! I’m not trying to talk anyone out of React.

My real reason is simpler, and a bit selfish as the person who owns the architecture:

I can hand this Vue + Quasar stack to a newer engineer and have them genuinely productive in under a week.

A lot of the architecture lives in the framework’s conventions, so there’s less “learn our project’s particular setup” stacked on top of “learn the language.” That is the difference between a new hire contributing this sprint and contributing next month.

I’m not experienced enough with React to claim you can’t get there too. I’d bet you can. I just know I don’t want to be the one assembling and re-teaching that recipe on every project, for every new person who joins.

So the takeaway isn’t that one beats the other:

  • React gives more freedom.

  • Vue with Quasar gives more defaults. Fewer wheels to invent or re-implement.

Freedom is great for some teams. Defaults are great for others. For the kind of teams I lead, defaults win more often.

Which comes down to one metaphor:

React gives me great ingredients. Quasar + Vue gives a kitchen, and a kitchen is a lot easier to share with a team.