Files
policy-ui/.claude/skills/nuxt/references/nuxt-composables.md
HaimKortovich a2eb1f3789 Add nuxt-skills and update auto quotes to use new policy API structure
- Add nuxt-skills (vue, nuxt, nuxt-ui) to .claude/skills/
- Create useCustomerSelection() composable for managing insured/buyer selection
- Create usePolicyApi() composable for policy API operations
- Update auto quote components to use insured/buyer instead of client
- Update vehicle fields: remove valorVehiculo, add market_value, requested_value, rc_limits
- Make chassis_number and engine_number optional
- Update auto quote types and composables to match new API structure
- Update auto quote page to submit to policy API with new structure
2026-04-27 14:56:53 -05:00

7.5 KiB

Nuxt Composables & Utilities

When to Use

Working with Nuxt-specific composables, URL handling, navigation, or data fetching.

URL & Request Handling

useRequestURL()

ALWAYS use useRequestURL() instead of window.origin or window.location:

// ✅ Correct - works SSR + client
const url = useRequestURL()
console.log(url.origin) // https://example.com
console.log(url.pathname) // /users/123
console.log(url.search) // ?tab=profile

// ❌ Wrong - breaks on SSR, not available server-side
const origin = window.origin
const path = window.location.pathname

Why: window is undefined during SSR. useRequestURL() works everywhere.

useRequestURL() Patterns

// Get full URL
const url = useRequestURL()
const fullUrl = url.href // https://example.com/users/123?tab=profile

// Get origin (base URL)
const baseUrl = url.origin // https://example.com

// Get path
const path = url.pathname // /users/123

// Get query params (use useRoute() instead for better typing)
const params = url.searchParams
const tab = params.get('tab') // 'profile'

// Build absolute URL
const apiUrl = `${url.origin}/api/users`

Navigation Composables

navigateTo()

// Navigate to route
await navigateTo('/about')

// Type-safe navigation
await navigateTo({ name: '/users/[userId]', params: { userId: '123' } })

// External URL
await navigateTo('https://nuxt.com', { external: true })

// Replace history
await navigateTo('/login', { replace: true })

// Open in new tab
await navigateTo('/docs', { open: { target: '_blank' } })

// Server-side redirect
return navigateTo('/login') // in middleware or server route

useRouter()

const router = useRouter()

// Navigate
router.push({ name: '/users/[userId]', params: { userId: '123' } })

// Go back
router.back()

// Go forward
router.forward()

// Navigation guards
router.beforeEach((to, from) => {
  // Guard logic
})

useRoute()

// Generic route
const route = useRoute()

// Typed route (preferred)
const route = useRoute('/users/[userId]')

// Access params
const userId = route.params.userId

// Access query
const tab = route.query.tab

// Access meta
const requiresAuth = route.meta.requiresAuth

Data Fetching

useFetch()

// Basic fetch
const { data, error, pending, refresh } = await useFetch('/api/users')

// With params
const { data } = await useFetch('/api/users', {
  query: { page: 1, limit: 10 }
})

// With key for deduplication
const { data } = await useFetch(`/api/users/${userId}`, {
  key: `user-${userId}`
})

// Lazy fetch (doesn't block navigation)
const { data } = await useLazyFetch('/api/users')

// Watch and refetch
const page = ref(1)
const { data } = await useFetch('/api/users', {
  query: { page },
  watch: [page]
})

// Cancel requests with AbortController signal (Nuxt 4.2+)
const controller = new AbortController()
const { data } = await useFetch('/api/users', {
  signal: controller.signal
})
// Later: controller.abort() to cancel the request

// Manual cancellation via execute/refresh
const { data, execute } = await useFetch('/api/users', { immediate: false })
const abortController = new AbortController()
await execute({ signal: abortController.signal })
// Later: abortController.abort() to cancel

useAsyncData()

// Custom async logic
const { data, error, pending, refresh } = await useAsyncData('users', async () => {
  const response = await $fetch('/api/users')
  return response.filter(u => u.active)
})

// Lazy version
const { data } = await useLazyAsyncData('users', async () => {
  return await $fetch('/api/users')
})

// Cancel with AbortController (Nuxt 4.2+)
const controller = new AbortController()
const { data } = await useAsyncData('users', async () => {
  return await $fetch('/api/users', { signal: controller.signal })
})
// Later: controller.abort() to cancel

// Custom cache logic with getCachedData
const { data } = await useAsyncData('users',
  async () => $fetch('/api/users'),
  {
    getCachedData: (key) => {
      // Return cached data or null/undefined to trigger fetch
      const cached = useNuxtData(key)
      return cached.data.value
    }
  }
)

// Deep reactivity for nested objects
// Default is shallow in Nuxt 4 (was deep in Nuxt 3)
const { data } = await useAsyncData('user',
  async () => $fetch('/api/user'),
  {
    deep: true // Makes nested properties reactive
  }
)

// Deduplication strategies (Nuxt 4.2+)
const { data } = await useAsyncData('users',
  async () => $fetch('/api/users'),
  {
    dedupe: 'cancel' // Cancel existing requests when new one starts
    // dedupe: 'defer' // Prevent new requests while one is pending
  }
)

// Manual cancellation via execute/refresh
const { data, execute } = await useAsyncData('users',
  async ({ signal }) => $fetch('/api/users', { signal }),
  { immediate: false }
)
const abortController = new AbortController()
await execute({ signal: abortController.signal })
// Later: abortController.abort() to cancel

State Management

useState()

// Create shared state
const counter = useState('counter', () => 0)

// Use in components
counter.value++

// With type
const user = useState<User | null>('user', () => null)

App Context

useNuxtApp()

const nuxtApp = useNuxtApp()

// Access provided values
const { $api, $hello } = nuxtApp

// Access hooks
nuxtApp.hook('page:finish', () => {
  console.log('Page loaded')
})

// Access Vue app
nuxtApp.vueApp.use(SomePlugin)

useRuntimeConfig()

// Access runtime config
const config = useRuntimeConfig()

// Public config (client + server)
const apiBase = config.public.apiBase

// Private config (server only)
const apiSecret = config.apiSecret // undefined on client

Head Management

useHead()

// Set page meta
useHead({
  title: 'User Profile',
  meta: [
    { name: 'description', content: 'View user profile' },
    { property: 'og:title', content: 'User Profile' }
  ],
  link: [
    { rel: 'canonical', href: 'https://example.com/profile' }
  ]
})

// Dynamic values
const user = ref({ name: 'John' })
useHead({
  title: () => `${user.value.name}'s Profile`
})

useSeoMeta()

// Cleaner SEO meta
useSeoMeta({
  title: 'User Profile',
  description: 'View user profile',
  ogTitle: 'User Profile',
  ogDescription: 'View user profile',
  ogImage: 'https://example.com/image.jpg',
  twitterCard: 'summary_large_image'
})

Best Practices

  • Use useRequestURL() NOT window.origin/location
  • Type routes with useRoute('/path/[param]')
  • Use useFetch for API calls (deduplication, SSR)
  • Key your fetches for proper caching
  • useState for shared state across components
  • useSeoMeta for cleaner SEO tags

Common Mistakes

Wrong Right
window.origin useRequestURL().origin
window.location.pathname useRequestURL().pathname
fetch() in components useFetch() or useAsyncData()
router.push('/path/' + id) router.push({ name: '/path/[id]', params: { id } })
Duplicate fetches Use key parameter

Resources