
September 10, 2026
12 min read
By Kokil Thapa | Last reviewed: September 2026
You picked up Vue because templates stay readable and the learning curve stays sane. Then your components grew past 300 lines, mixins collided, and TypeScript felt bolted on. The Vue 3 Composition API guide you actually need starts there: it replaces the Options API's scattered data, methods, and mounted blocks with grouped, reusable logic inside <script setup>. On production Laravel apps I maintain, that shift cut duplicate booking-form code and made Livewire-plus-Vue islands easier to reason about. This walkthrough covers setup through composables, with patterns you can ship this week.
ref, reactive, computed, and watch—inside <script setup>, grouped by feature instead of option type. Use composables to share stateful logic across components without mixins.For Laravel-backed SPAs and partial Vue islands, see the Vue with Laravel setup guide. If you type your frontends, pair this page with TypeScript with Vue 3 best practices.
What is the Vue 3 Composition API and how does it differ from Options API?
The Composition API is a set of importable functions for declaring reactive state, derived values, watchers, and lifecycle hooks. You call them inside setup() or, more often, directly in <script setup>. Logic that belongs together—fetching a user profile, validating a form, syncing query params—sits in one block instead of three separate option keys.
The Options API is still valid. Vue 3 supports both in the same codebase. New feature work on teams I work with almost always uses Composition API because composables replace mixins cleanly and IDE autocomplete improves with typed refs.
| Criteria | Options API | Composition API |
|---|---|---|
| Logic grouping | Split across data, methods, computed | Grouped by feature or domain |
| Code reuse | Mixins with implicit naming conflicts | Composables with explicit exports |
| TypeScript | Works, but inference is weaker | Stronger inference with typed refs |
| Bundle size | Entire option object shape | Tree-shakeable function imports |
| Learning curve | Lower for small components | Steeper start, pays off past ~150 lines |
| Vue 3 default for new code | Supported, not recommended for greenfield | Recommended in official docs |
The official Composition API FAQ from Vue.js states both APIs are first-class. Pick Composition API when components carry multiple concerns—search filters plus pagination plus export, typical on admin dashboards and booking UIs.
When Options API still makes sense
Keep Options API for tiny presentational components: a badge, a static card, a one-field toggle. A 40-line Options component is often clearer than the same file with five imports. Migrate incrementally; Vue does not force a full rewrite.
How do you set up a Vue 3 project with the Composition API?
Greenfield Vue 3 apps use Vite 8.x and npm 12. Laravel 13 projects typically embed Vue via Vite in resources/js. The Composition API needs no extra plugin—it ships with Vue 3.
Scaffold with Vite
npm create vue@latest my-app
cd my-app
npm install
npm run dev
Select TypeScript if your team uses it. Choose "Composition API" when the CLI asks—this generates <script setup> stubs. Node.js 26 LTS is the current LTS target for frontend tooling in 2026.
Minimal script setup component
<script setup>
import { ref, computed, onMounted } from 'vue'
const count = ref(0)
const doubled = computed(() => count.value * 2)
function increment() {
count.value++
}
onMounted(() => {
console.log('mounted with count', count.value)
})
</script>
<template>
<button @click="increment">{{ count }} (×2 = {{ doubled }})</button>
</template>
Everything declared at the top level of <script setup> is exposed to the template automatically. No return statement. No export default { setup() {} } boilerplate.
Laravel Vite entry point
On a Laravel 12 or 13 app with PHP 8.3+, register your root component in resources/js/app.js:
import { createApp } from 'vue'
import BookingWidget from './components/BookingWidget.vue'
const el = document.getElementById('booking-widget')
if (el) {
createApp(BookingWidget, {
trekId: el.dataset.trekId,
}).mount(el)
}
Pass server-rendered data through data-* attributes or a small JSON blob in a <script type="application/json"> tag. Never embed secrets in the DOM. For deeper integration patterns, read the Vue 3 Composition API deep dive on this site.
Which Composition API functions should you use for state, computed values, and side effects?
Vue splits reactivity into primitives. Pick the wrong one and you lose reactivity silently—a common production bug.
ref versus reactive
ref(value)— wraps any value; access via.valuein script, auto-unwrapped in templates. Use for primitives and when you may replace the whole object.reactive(object)— deep proxy for objects only. Cannot reassign the root reference. Fine for fixed-shape form objects.computed(fn)— cached derived state. Use for filtered lists, totals, validation summaries.watch(source, callback)— run side effects when data changes. Use for API calls, localStorage sync, route reactions.watchEffect(fn)— auto-tracks dependencies inside the callback. Use sparingly; explicitwatchis easier to debug.
<script setup>
import { ref, reactive, computed, watch } from 'vue'
const search = ref('')
const filters = reactive({ category: 'all', inStock: true })
const filteredProducts = computed(() => {
return products.value.filter(p => {
if (filters.inStock && !p.inStock) return false
if (filters.category !== 'all' && p.category !== filters.category) return false
return p.name.toLowerCase().includes(search.value.toLowerCase())
})
})
watch(search, (newVal) => {
history.replaceState(null, '', `?q=${encodeURIComponent(newVal)}`)
})
</script>
The Vue reactivity core API reference documents edge cases: ref unwrapping in reactive objects, toRef, and toRefs for destructuring without breaking proxies.
Lifecycle and side-effect hooks
onMounted— DOM ready; fetch initial data, attach non-Vue listeners.onUpdated— after DOM patch; use rarely, prefer watchers.onUnmounted— cleanup intervals, abort fetch controllers, remove listeners.onBeforeUnmount— last chance to persist draft state.
import { onMounted, onUnmounted } from 'vue'
let controller = null
onMounted(async () => {
controller = new AbortController()
const res = await fetch('/api/treks', { signal: controller.signal })
treks.value = await res.json()
})
onUnmounted(() => {
controller?.abort()
})
Always abort in-flight fetches when the user navigates away. I've seen ghost toasts and race-condition bugs on booking widgets without this step.
Props, emits, and defineModel
<script setup>
const props = defineProps({
trekId: { type: Number, required: true },
currency: { type: String, default: 'NPR' },
})
const emit = defineEmits(['booked', 'cancel'])
const guestCount = defineModel('guestCount', { type: Number, default: 1 })
</script>
defineModel (Vue 3.4+) replaces verbose v-model prop-plus-emit pairs. For complex forms, validate props with the same rules you enforce server-side in Laravel Form Requests.
How do you organize reusable logic with composables in Vue 3?
A composable is a function named useSomething that calls Composition API functions and returns refs, methods, or computed values. It replaces mixins without magic property merging.
Example: useApi composable
// composables/useApi.js
import { ref, shallowRef } from 'vue'
export function useApi(url) {
const data = shallowRef(null)
const error = ref(null)
const loading = ref(false)
async function execute(options = {}) {
loading.value = true
error.value = null
try {
const res = await fetch(url, options)
if (!res.ok) throw new Error(`HTTP ${res.status}`)
data.value = await res.json()
} catch (e) {
error.value = e.message
} finally {
loading.value = false
}
}
return { data, error, loading, execute }
}
<script setup>
import { useApi } from '@/composables/useApi'
const { data: treks, loading, error, execute } = useApi('/api/treks')
execute()
</script>
Use shallowRef for large API payloads. Deep reactivity on thousand-row arrays costs memory and CPU. Pair API composables with Laravel Sanctum cookie auth or Bearer tokens as described in Laravel API best practices.
Pinia for shared global state
Composables isolate per-component state. When many routes need the same cart or auth session, use Pinia—a store designed for Composition API:
// stores/cart.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useCartStore = defineStore('cart', () => {
const items = ref([])
const total = computed(() => items.value.reduce((s, i) => s + i.price, 0))
function addItem(product) { items.value.push(product) }
return { items, total, addItem }
})
On the Quick And Easy Nepalese Grocery Laravel storefront, Pinia plus composables kept delivery-zone logic out of Vue components. The pattern maps cleanly to multi-zone eCommerce builds in the portfolio.
Naming and file conventions
- Prefix composables with
use—useDebounce,useLocalStorage. - One composable per file under
resources/js/composables/. - Return plain objects; destructure in components knowing destructured refs stay reactive only with
storeToRefsfor Pinia. - Test composables with Vitest by calling the function directly—no mount required.
Debug JSON responses from your API endpoints with the site JSON formatter tool before wiring them into composables.
How does the Vue 3 Composition API work with Laravel and production apps?
Most of my Vue work sits inside Laravel Blade apps, not standalone SPAs. Vue islands handle interactive slices: date pickers with Bikram Sambat conversion, document upload progress, live search on lawyer directories. The Composition API keeps each island self-contained.
Security and XSS
Never bind unsanitized HTML with v-html on user content. Laravel escapes Blade output by default; Vue does not. Read XSS prevention with Blade and Vue before shipping rich text from CMS fields.
Routing with Vue Router 4
Full SPAs mount Vue Router at the Laravel catch-all route. Composition API components use useRoute and useRouter from vue-router. Advanced lazy-loading and guard patterns live in the Vue Router 4 advanced patterns article.
Performance checklist
- Lazy-load heavy components with
defineAsyncComponent. - Prefer
v-showoverv-iffor toggles that flip often. - Virtualise long lists with a virtual scroller instead of rendering 2,000 DOM nodes.
- Commit production Vite builds; do not run Node on shared hosting.
- Validate bundle size in CI—Composition API tree-shaking helps, but importing all of lodash does not.
For performance audits on live sites, see speed optimization services and testing and optimization.
Real project pattern: booking widget
On a trekking booking Laravel app with Livewire for admin and Vue for the public date picker, the Composition API composable useAvailability centralised fetch logic, debounced calendar clicks, and NPR/USD price display. Server rules—minimum group size, blackout dates—still validate in Laravel controllers. The widget only handles UX state.
That split mirrors work on Adventure Third Pole Trek and similar booking platforms. Need a team to wire this up? Review web development services or custom software development.
Date handling across Nepali and Gregorian calendars belongs on the server or in tested utilities. For client-side experiments, compare approaches in JavaScript date handling with the Temporal API. Use the Nepali date converter to verify conversions against known BS dates.
Validate regex for slug and phone fields with the regex tester before embedding patterns in composables. For API contract details behind your composables, see building RESTful APIs with Laravel and API development services.
Legal-tech portals like Court Marriage In Nepal use Vue sparingly—lead forms and document upload progress—while Blade carries SEO content. That hybrid keeps Core Web Vitals stable and crawl budget focused on informational pages, a pattern covered in technical SEO services.
More background on the author approach to full-stack delivery lives on the about page. Browse the full project portfolio for Laravel plus Vue combinations across eCommerce, legal, and travel verticals.
Key Takeaways
- Use
<script setup>and Composition API functions for any component past roughly 150 lines or with multiple concerns. - Prefer
reffor most state; reach forreactiveonly when the object shape is stable and you will not reassign the root. - Extract repeated logic into
use*composables instead of mixins; use Pinia when state must span routes. - Abort fetches on unmount, use
shallowReffor large lists, and never trust client validation alone on Laravel backends. - Build Vue assets with Vite 8.x via npm 12, commit artefacts for Deployer-style servers, and mount Vue as islands inside Blade for SEO-heavy sites.
- Read the official Vue docs alongside Laravel API and XSS guides before shipping user-generated content to the DOM.
People Also Ask
Is the Composition API required in Vue 3?
No. Vue 3 fully supports the Options API. The Composition API is recommended for new components because composables scale better and TypeScript integration is stronger. You can mix both styles in one project during migration.
Should beginners learn Options API or Composition API first?
Learn Composition API with <script setup> first in 2026. Official Vue docs centre on it. Understand Options API enough to maintain legacy code—you will encounter it in older tutorials and codebases.
What is the difference between setup() and script setup?
setup() is a function inside export default that returns data for the template. <script setup> is syntactic sugar: top-level bindings auto-expose to the template and reduce boilerplate. New code should use <script setup> unless you need a specific Options API merge edge case.
Can you use Vue 3 Composition API without TypeScript?
Yes. Plain JavaScript works. TypeScript adds prop typing, composable return types, and safer ref inference. For greenfield apps, enable TypeScript at scaffold time—the incremental cost is low and pays off in composable-heavy codebases.
Ship Vue 3 components that survive production
This Vue 3 Composition API guide gives you the primitives: script setup, refs, composables, and Laravel integration patterns that hold up after deploy. Start one component—extract one composable—then expand. If you want help wiring Vue islands into a Laravel app, booking flow, or eCommerce cart, contact us to discuss architecture, build, and deployment on your stack.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

