Vue & Nuxt tips

Code examples and notes on Vue, Nuxt, and Vite.

computed()
const count = ref(1)
const doubled = computed(
  () => count.value * 2
)
doubled 2

Tips

53 tips
01
vite

Import a file as text with a single suffix

Add ?raw to import a file's contents as a string. Handy for showing source code or loading a shader without an extra request at runtime.

ts
import example from './examples/Counter.vue?raw'
import shader from './shaders/gradient.glsl?raw'

// Both imports are strings
console.log(example)
console.log(shader)

In Vue, render imported text with interpolation to keep it escaped.

vue
<template>
  <pre><code>{{ example }}</code></pre>
</template>

Use ?url instead when you need the asset URL. Imported content is shipped to the browser, so keep private files out of these imports.

Read the Vite documentation.

02
vue

Accept a value, ref, or getter in the same composable

In Vue 3.3+, toValue() unwraps refs and calls getters. Run it inside a reactive effect so changing inputs remain tracked.

ts
import { computed, ref, toValue } from 'vue'
import type { MaybeRefOrGetter } from 'vue'

export function useGreeting(
  name: MaybeRefOrGetter<string>
) {
  return computed(() => `Hello, ${toValue(name)}!`)
}

// All three input shapes work
const nameRef = ref('Michał')
useGreeting('Michał')
useGreeting(nameRef)
useGreeting(() => nameRef.value.toUpperCase())

Unlike unref(), toValue() also evaluates getter functions. Passing props.name directly would only pass its current value.

Read the Vue documentation.

03
vue

Pause a watcher while you batch changes

Vue 3.5+ watchers return pause, resume, and stop. Pause a reaction while updating related values, then resume it with the final state.

vue
<script setup>
import { ref, watch } from 'vue'

const filters = ref({ query: '', page: 1 })
const appliedFilters = ref({ ...filters.value })

const { pause, resume } = watch(filters, (value) => {
  appliedFilters.value = { ...value }
}, { deep: true })

function resetFilters() {
  pause()
  filters.value.query = ''
  filters.value.page = 1
  resume()
}
</script>

Vue already batches synchronous watcher callbacks. This control is useful when a pause needs to span separate steps. It does not cancel work a previous callback already started.

Read the Vue documentation.

04
vue

Let a watcher stop itself after the first change

In Vue 3.4+, once: true stops a watcher after its first callback. Useful for an interaction that should happen only once per component instance.

vue
<script setup>
import { ref, watch } from 'vue'

const hasEdited = ref(false)
const showHint = ref(true)

watch(hasEdited, () => {
  showHint.value = false
}, { once: true })
</script>

<template>
  <input @input="hasEdited = true">
  <p v-if="showHint">Start typing to edit.</p>
</template>

Adding immediate: true runs the callback immediately and uses up that one execution.

Read the Vue documentation.

05
vue

Watch array changes without traversing every nested object

Vue 3.5+ lets you limit watcher traversal with a number. Use deep: 1 for an array when you care about its entries, not changes inside each entry.

vue
<script setup>
import { ref, watch } from 'vue'

const items = ref([{ id: 1, name: 'First' }])

watch(items, () => {
  console.log('Array changed')
}, { deep: 1 })

// Triggers the watcher
items.value.push({ id: 2, name: 'Second' })

// Does not trigger it
items.value[0].name = 'Renamed'
</script>

Read the Vue documentation.

06
nuxt

Nuxt 3.14 introduces the `shared/` folder for types and utilities that work in both server and client contexts with auto-imports.

text
project/
  shared/
    utils/
      format.ts            # Auto-imported everywhere
    types/
      index.ts             # Shared type definitions
  server/
    api/
      users.get.ts         # Can use shared utils
  app.vue                  # Can also use shared utils
ts
// shared/utils/format.ts
export function formatCurrency(amount: number, currency = 'USD') {
  return new Intl.NumberFormat('en-US', {
    style: 'currency', currency
  }).format(amount)
}
vue
<!-- app.vue — auto-imported, no import needed -->
<template>
  <p>{{ formatCurrency(99.99) }}</p>
</template>
ts
// server/api/invoice.get.ts — same auto-import works server-side
export default defineEventHandler(() => {
  return { total: formatCurrency(250) }
})
07
nuxt

Nuxt 3.13 lets you control when `NuxtLink` prefetches — on hover/focus interaction, on viewport visibility, or both.

vue
<template>
  <!-- Default: prefetch when link is visible in viewport -->
  <NuxtLink to="/about">About</NuxtLink>

  <!-- Prefetch only on hover or focus (saves bandwidth) -->
  <NuxtLink to="/heavy-page" prefetch-on="interaction">
    Heavy Page
  </NuxtLink>

  <!-- Prefetch on both visibility AND interaction -->
  <NuxtLink
    to="/dashboard"
    :prefetch-on="{ visibility: true, interaction: true }"
  >
    Dashboard
  </NuxtLink>
</template>
ts
// nuxt.config.ts — set a global default for all NuxtLinks
export default defineNuxtConfig({
  experimental: {
    defaults: {
      nuxtLink: {
        prefetch: true,
        prefetchOn: { visibility: false, interaction: true },
      },
    },
  },
})
08
nuxt

Nuxt 3.13 supports route groups using parentheses in folder names — organize pages logically without changing the URL structure.

text
pages/
  index.vue                  →  /
  (marketing)/
    about.vue                →  /about
    contact.vue              →  /contact
  (shop)/
    products.vue             →  /products
    cart.vue                 →  /cart
  (auth)/
    login.vue                →  /login
    register.vue             →  /register
vue
<!-- pages/(marketing)/about.vue -->
<template>
  <div>
    <!-- URL is /about, NOT /(marketing)/about -->
    <h1>About Us</h1>
  </div>
</template>
09
nuxt

Nuxt 3.11's `usePreviewMode()` composable lets you toggle preview mode for draft content with a single call.

vue
<!-- pages/[...slug].vue -->
<script setup>
const { enabled, state } = usePreviewMode()
</script>

<template>
  <div>
    <div v-if="enabled" class="preview-banner">
      Preview mode is active
    </div>
    <ContentRenderer :value="page" />
  </div>
</template>
ts
// Activate by visiting: /my-page?preview=true&token=my-secret

// You can also customize the enable check:
const { enabled, state } = usePreviewMode({
  shouldEnable: () => {
    return route.query.preview === 'true'
      && route.query.token === 'my-secret'
  },
  getState: (currentState) => {
    return { token: route.query.token, ...currentState }
  },
})
10
vue

Vue 3.5 supports built-in lazy hydration strategies for async components — hydrate only when visible, when idle, or on interaction.

vue
<script setup>
import {
  defineAsyncComponent,
  hydrateOnVisible,
  hydrateOnIdle,
  hydrateOnInteraction,
} from 'vue'

// Hydrate when the component scrolls into view
const HeavyChart = defineAsyncComponent({
  loader: () => import('./HeavyChart.vue'),
  hydrate: hydrateOnVisible(),
})

// Hydrate when the browser is idle
const AdBanner = defineAsyncComponent({
  loader: () => import('./AdBanner.vue'),
  hydrate: hydrateOnIdle(5000),
})

// Hydrate on specific user interactions
const Dropdown = defineAsyncComponent({
  loader: () => import('./Dropdown.vue'),
  hydrate: hydrateOnInteraction(['click', 'mouseover']),
})
</script>

<template>
  <HeavyChart />
  <AdBanner />
  <Dropdown />
</template>
11
vue

Vue 3.5's `<Teleport defer>` waits until the current render cycle is complete, so you can teleport to a target rendered later in the same template.

vue
<!-- Before (Vue 3.4) — this FAILS because #container doesn't exist yet -->
<template>
  <Teleport to="#container">
    <p>Teleported content</p>
  </Teleport>
  <div id="container"></div>
</template>
vue
<!-- After (Vue 3.5+) — defer waits for the full render cycle -->
<template>
  <Teleport defer to="#container">
    <p>Teleported content</p>
  </Teleport>
  <div id="container"></div>
</template>
12
vue

Vue 3.5's `onWatcherCleanup()` lets you register cleanup callbacks inside watchers — perfect for aborting stale API requests.

vue
<script setup>
import { ref, watch, onWatcherCleanup } from 'vue'

const userId = ref(1)

watch(userId, (newId) => {
  const controller = new AbortController()

  fetch(`/api/users/${newId}`, { signal: controller.signal })
    .then(r => r.json())
    .then(data => { /* handle data */ })

  // If userId changes before fetch completes, abort the previous request
  onWatcherCleanup(() => {
    controller.abort()
  })
})
</script>
vue
<!-- Also works inside watchEffect -->
<script setup>
import { ref, watchEffect, onWatcherCleanup } from 'vue'

const searchQuery = ref('')

watchEffect(() => {
  const controller = new AbortController()

  fetch(`/api/search?q=${searchQuery.value}`, {
    signal: controller.signal
  })
    .then(r => r.json())
    .then(data => { /* update results */ })

  onWatcherCleanup(() => controller.abort())
})
</script>
13
vue

Vue 3.5's `useId()` generates unique IDs that are stable across server and client renders — perfect for form accessibility.

vue
<script setup>
import { useId } from 'vue'

const id = useId()
</script>

<template>
  <form>
    <label :for="id">Email:</label>
    <input :id="id" type="email" />
  </form>
</template>
vue
<!-- Each component instance gets its own unique ID -->
<script setup>
import { useId } from 'vue'

defineProps<{ label: string }>()

const id = useId()
</script>

<template>
  <div>
    <label :for="id">{{ label }}</label>
    <select :id="id">
      <slot />
    </select>
  </div>
</template>
14
vue

Vue 3.5 introduces `useTemplateRef()` — a cleaner, type-safe way to access template refs without relying on matching variable names.

vue
<!-- Before (Vue 3.4) -->
<script setup>
import { ref, onMounted } from 'vue'

// variable name MUST match the ref attribute
const inputEl = ref(null)

onMounted(() => {
  inputEl.value.focus()
})
</script>

<template>
  <input ref="inputEl" />
</template>
vue
<!-- After (Vue 3.5+) -->
<script setup>
import { useTemplateRef, onMounted } from 'vue'

const input = useTemplateRef('my-input')

onMounted(() => {
  input.value.focus()
})
</script>

<template>
  <input ref="my-input" />
</template>
15
vue

In Vue 3.5, you can destructure props with reactive default values using native JavaScript syntax — no more `withDefaults()` needed.

vue
<!-- Before (Vue 3.4) -->
<script setup lang="ts">
const props = withDefaults(
  defineProps<{
    count?: number
    message?: string
  }>(),
  {
    count: 0,
    message: 'hello',
  }
)
</script>
vue
<!-- After (Vue 3.5+) -->
<script setup lang="ts">
const { count = 0, message = 'hello' } = defineProps<{
  count?: number
  message?: string
}>()
</script>

<template>
  <p>{{ count }} - {{ message }}</p>
</template>
16
vue

Starting in Vue 3.4, the recommended approach to achieve two-way data binding is using the `defineModel()` macro.

vue
<!-- before defineModel -->
<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
</script>

<template>
  <input
    :value="props.modelValue"
    @input="emit('update:modelValue', $event.target.value)"
  />
</template>
vue

<!-- after defineModel -->
<script setup>
const model = defineModel();
</script>

<template>
  <input v-model="model" />
</template>
17
vue

One of the most common mistakes I see across Vue codebases is the misuse of `ref()`. You don't have to wrap every variable in `ref()`, only wrap it when you need reactivity. 💡

vue
<script setup>
// this doesn't need to be wrapped in ref()
// no need for reactivity here
const links = [
  {
    name: 'about',
    href: '/about'
  },
  {
    name: 'terms of service',
    href: '/tos'
  },
  {
    name: 'contact us',
    href: '/contact'
  }
]

// isActive flag needs to be reactive to reflect UI changes
// that's why it's a good idea to wrap tabs into ref
const tabs = ref([
  {
    name: 'Privacy',
    url: '/privacy',
    isActive: true
  },
  {
    name: 'Permissions',
    url: '/permissions',
    isActive: false
  }
])
</script>
18
vue

From Vue 3.4, you can make use of v-bind same name shorthand

vue
<template>
<!-- You can now shorten this: -->
<img :id="id" :src="src" :alt="alt">

<!-- To this: -->
<img :id :src :alt>
</template>
19
vue

In Vue, when you are using large data structures and you don't need deep reactivity, you can make use of shallowRef instead of ref.

js
const state = shallowRef({ count: 1 })

// does NOT trigger change
state.value.count = 2

// does trigger change
state.value = { count: 2 }
20
vue

In Vue, you can type your component emits to have better error handling and editor support.

js
const emit = defineEmits<{
  change: [id: number]
  update: [value: string]
}>()
21
vue

In Vue, you can use any dynamic value directly in your `<style>` thanks to the `v-bind` directive. It is fully reactive.

vue
<style scoped>
button {
  background-color: v-bind(backgroundColor);
}
</style>
22
vue

Handle loading and errors with TanStack Vue Query

After installing @tanstack/vue-query and registering VueQueryPlugin on your Vue app, useQuery() manages request state and caches the result by its query key.

vue
<script setup>
import { useQuery } from '@tanstack/vue-query'

async function fetchPosts() {
  const response = await fetch('/api/posts')
  if (!response.ok) {
    throw new Error(`Request failed: ${response.status}`)
  }
  return response.json()
}

const { data: posts, isPending, isError, error } = useQuery({
  queryKey: ['posts'],
  queryFn: fetchPosts
})
</script>

<template>
  <p v-if="isPending">Loading posts...</p>
  <p v-else-if="isError">{{ error.message }}</p>
  <ul v-else>
    <li v-for="post in posts" :key="post.id">{{ post.title }}</li>
  </ul>
</template>

This example uses the v5 API and expects /api/posts to return an array of posts. A failed HTTP response must throw so the query enters its error state.

TanStack Vue Query documentation.

23
vue

In Vue, when you're using scoped styles but want just one rule to apply globally, you can use the `:global` pseudo-class instead of creating another <style>.

vue
<style scoped>
:global(.red) {
  color: red;
}
</style>
24
nuxt

In Nuxt, you can reduce CLS by using local font fallbacks thanks to the awesome Fontaine library.

install the package

bash
npm install -D @nuxtjs/fontaine

and set the module inside nuxt.config

js
export default defineNuxtConfig({
  modules: ['@nuxtjs/fontaine'],
})

And that's it!

25
vue

You can set the default values for your props even when you are using `defineProps` with type-only declaration. This is possible thanks to the `withDefaults` macro.

vue
<script setup lang="ts">
export interface Props {
  variant?: 'primary' | 'secondary'
  disabled?: boolean
}

const props = withDefaults(defineProps<Props>(), {
  variant: 'primary',
  disabled: false
})
</script>
26
nuxt

Search a Nuxt Content collection

In Nuxt Content v3, queryCollectionSearchSections() returns searchable text. This example filters it in the browser. Replace content with your collection name.

vue
<script setup lang="ts">
const search = ref('')
const { data: sections } = await useAsyncData(
  'content-search',
  () => queryCollectionSearchSections('content')
)

const results = computed(() => {
  const query = search.value.trim().toLowerCase()
  if (!query) return []

  return (sections.value ?? []).filter(section =>
    `${section.title} ${section.content}`
      .toLowerCase().includes(query)
  )
})
</script>

<template>
  <label for="search">Search documentation</label>
  <input id="search" v-model="search" type="search">
  <ul>
    <li v-for="result in results" :key="result.id">
      <NuxtLink :to="result.id">{{ result.title }}</NuxtLink>
    </li>
  </ul>
</template>

This downloads the search sections to the client. For a large collection, use server-side search instead.

Nuxt Content documentation.

27
nuxt

Link to the previous and next article in Nuxt Content

In Nuxt Content v3, queryCollectionItemSurroundings() returns the entries on either side of the current page. Use the same ordering as your article list.

vue
<script setup lang="ts">
const route = useRoute()
const { data: surround } = await useAsyncData(
  `surround-${route.path}`,
  () => queryCollectionItemSurroundings('content', route.path)
    .order('title', 'ASC')
)
</script>

<template>
  <nav aria-label="Article navigation">
    <NuxtLink v-if="surround?.[0]" :to="surround[0].path">
      ← {{ surround[0].title }}
    </NuxtLink>
    <NuxtLink v-if="surround?.[1]" :to="surround[1].path">
      {{ surround[1].title }} →
    </NuxtLink>
  </nav>
</template>

Here, content is a page collection. The first or last result can be null at the edges of the list.

Nuxt Content documentation.

28
vue

In Vue, you can easily register a custom directive by creating an object containing lifecycle hooks with the 'v-' prefix.

vue
<script setup>
// enables v-focus in templates
const vFocus = {
  mounted: (el) => el.focus()
}
</script>

<template>
  <input v-focus />
</template>
29
vite

Configure an import alias in Vite and TypeScript

Configure the alias in Vite, then add the matching TypeScript path so the editor resolves the same imports.

ts
// vite.config.ts
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  }
})

Add this to tsconfig.app.json, or the tsconfig that includes your source files.

json
{
  "compilerOptions": {
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

Merge this into your existing configuration. The example assumes the config file sits at the project root.

ts
import Button from '@/components/Button.vue'

Vite alias documentation.

30
nuxt

Add content pages to a Nuxt sitemap

Nuxt Sitemap can read URLs from a server endpoint. With Nuxt Content v3, query your page collection and return each entry's path as loc.

sh
npm install @nuxtjs/sitemap
ts
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@nuxt/content', '@nuxtjs/sitemap'],
  site: { url: 'https://example.com' },
  sitemap: { sources: ['/api/sitemap-urls'] }
})
ts
// server/api/sitemap-urls.ts
import { queryCollection } from '@nuxt/content/server'

export default defineEventHandler(async (event) => {
  const pages = await queryCollection(event, 'content')
    .select('path')
    .all()

  return pages.map(page => ({ loc: page.path }))
})

Replace the domain and collection name with your own. Only include public pages. If you provide lastmod, use the page's actual modification date, not the time of the request.

Nuxt Sitemap documentation.

31
nuxt

Generate an RSS feed from Nuxt Content

Query a Nuxt Content v3 page collection from a server route and pass the results to rss.

sh
npm install rss
npm install -D @types/rss
ts
// server/routes/rss.xml.get.ts
import RSS from 'rss'
import { queryCollection } from '@nuxt/content/server'

export default defineEventHandler(async (event) => {
  const siteUrl = 'https://example.com'
  const feed = new RSS({
    title: 'Engineering notes',
    site_url: siteUrl,
    feed_url: `${siteUrl}/rss.xml`
  })

  const pages = await queryCollection(event, 'content').all()

  for (const page of pages) {
    const url = new URL(page.path, siteUrl).href
    feed.item({
      title: page.title,
      description: page.description ?? '',
      url,
      guid: url
    })
  }

  setHeader(event, 'content-type', 'application/rss+xml; charset=utf-8')
  return feed.xml({ indent: true })
})

Use your own domain and collection name, and query only public entries. If your schema includes publication dates, pass them as each item's date and sort the query by that field.

Nuxt Content server queries · RSS package documentation.

32
vue

If you want a CSS selector in scoped styles to be "deep", i.e. affecting child components, you can use the :deep() pseudo-class.

vue
<style scoped>
.a :deep(.b) {
  /* ... */
}
</style>
33
vue

By default, scoped styles do not affect contents rendered by <slot/>. To explicitly target slot content, use the :slotted pseudo-class.

vue
<style scoped>
:slotted(div) {
  color: red;
}
</style>
34
vue

In Vue.js, an active component instance will be unmounted when switching away from it by default. But what if you want to preserve the state when switching components? You can wrap it with the built-in <KeepAlive> component to preserve and cache the state. 💪🏻

vue
<template>
<KeepAlive>
  <component :is="activeComponent" />
</KeepAlive>
</template>
35
vue

In Vue.js, you can pass multiple named slots to your child components.

vue
<!-- Child component / Input.vue -->
<template>
  <div class="input-wrapper">
    <label>
      <slot name="label" />
    </label>
    <input />
    <div class="input-icon">
      <slot name="icon" />
    </div>
  </div>
</template>

<!-- Parent component -->
<template>
  <Input>
    <template #label>
      Email
    </template>
    <template #icon>
      <EmailIcon />
    </template>
  </Input>
</template>
36
vue

Thanks to the experimental component called 'Suspense', you can orchestrate async dependencies in a component tree. It can render a loading state while waiting for multiple nested async dependencies down the component tree to be resolved.

vue
<template>
  <Suspense>
    <!-- component with nested async dependencies -->
    <Dashboard />

    <!-- loading state via #fallback slot -->
    <template #fallback>
      Loading...
    </template>

  </Suspense>
</template>
37
nuxt

Nuxt Island is a specially built-in component that allows you to render the component entirely on the server, which means zero client-side JavaScript is served to the browser.

js
// This feature is still experimental so you have to enable it in nuxt.config
export default defineNuxtConfig({
  experimental: {
    componentIslands: true
  }
})

Let's say that you have a JS-rich component, but you don't need the code of that library in your production bundle. One example could be using a heavy date manipulation library like moment.js. We just want to format some data and show users the result. It's a perfect use case for server components. You are running JS on the server and returning HTML without any JS to the browser.

vue
<!-- components/Hello.vue -->
<template>
    <div>
        <h1>Hello</h1>
        {{ date }}
    </div>
</template>

<script setup lang="ts">
import moment from 'moment';
const date = moment().format('MMMM Do YYYY, h:mm:ss a');
</script>

All you have to do is move your component into the /components/islands directory and then call the component.

vue
<!-- app.vue -->
<template>
    <NuxtIsland name="Hello" />
</template>
38
vue

In Vue, you can "teleport" a part of a component's template into a DOM node that exists outside the DOM hierarchy of that component. To do this, use the built-in Teleport component and target the specific DOM element you want to teleport the part of your template to.

vue
<template>
  <Teleport to="body">
    <div v-if="open" class="modal">
      <p>Hello from the modal!</p>
      <button @click="open = false">Close</button>
    </div>
  </Teleport>
  </template>
39
vue

In Vue, you can enable performance tracing in the browser devtool performance/timeline panel. This only works in development mode.

js
const app = createApp(App);

app.config.performance = true;

app.mount('#app');
40
vue

Switch between component definitions with shallowRef

Use shallowRef() when storing a component definition in reactive state. Replacing the active component updates the view without turning the definition into a reactive proxy.

vue
<script setup>
import { shallowRef } from 'vue'
import UserSettings from './UserSettings.vue'
import UserNotifications from './UserNotifications.vue'

const activeComponent = shallowRef(UserSettings)
</script>

<template>
  <button @click="activeComponent = UserSettings">Settings</button>
  <button @click="activeComponent = UserNotifications">Notifications</button>
  <component :is="activeComponent" />
</template>

Switching unmounts the previous component. Wrap the dynamic component in KeepAlive if you need to preserve its local state.

Vue reactivity documentation.

41
nuxt

Thanks to the 'callOnce' utility in Nuxt, you can execute a specified function or block of code once during server-side rendering (SSR) or client-side rendering (CSR).

vue
<script setup lang="ts">
const websiteConfig = useState('config')

await callOnce(async () => {
  console.log('This will only be logged once')
  websiteConfig.value = await $fetch('https://my-cms.com/api/website-config')
})
</script>
42
vue

In Vue, when you are passing a boolean type as a prop with an explicit true value, you can use the following shorthand.

vue
<template>
  <!-- you can use this -->
  <BlogPost is-published />

  <!-- instead of this -->
  <BlogPost :is-published="true" />
</template>
43
vue

By default, v-model syncs the input with the data after each input event. You can add the lazy modifier to instead sync after change events.

html
<!-- synced after "change" instead of "input" -->
<input v-model.lazy="msg" />
44
vue

If you want user input to be automatically typecast as a number, you can add the number modifier to your v-model managed inputs.

html
<input v-model.number="age" />
45
vue

If you want whitespace from user input to be trimmed automatically, you can add the trim modifier to your v-model-managed inputs.

html
<input v-model.trim="msg" />
46
nuxt

You can expose your Nuxt development server to the internet thanks to local tunneling. All you have to do is run the Nuxt development server with the `--tunnel` flag.

bash
npx nuxt dev --tunnel
47
nuxt

You can run your Nuxt development server on the HTTPS protocol with a self-signed certificate.

bash
npx nuxt dev --https
48
vue

Components using `<script setup>` are closed by default. To explicitly expose properties in a `<script setup>` component, use the defineExpose compiler macro.

vue
<script setup>
import { ref } from 'vue'

const a = 1
const b = ref(2)

defineExpose({
  a,
  b
})
</script>
49
nuxt

In Nuxt, sometimes you would like to refresh the cookie value returned by a 'useCookie' composable. You can use the `refreshCookie` utility function available from Nuxt 3.10.

vue
<script setup lang="ts">
const tokenCookie = useCookie('token')

const login = async (username, password) => {
  const token = await $fetch('/api/token', { ... }) // Sets `token` cookie on response
  refreshCookie('token')
}

const loggedIn = computed(() => !!tokenCookie.value)
</script>
50
vue

In Vue, when your component template has some classes and you also add some classes to this component in the parent, the classes will be merged together.

parent component

vue
<template>
  <Table class="py-2"></Table>
</template>

child component Table.vue

vue
<template>
  <table class="border-solid border-2 border-sky-500">
    <!-- ... -->
  </table>
</template>

classes from the parent and child will be merged together

vue
<template>
  <table class="border-solid border-2 border-sky-500 py-2">
    <!-- ... -->
  </table>
</template>
51
vite

Use Lightning CSS to transform and minify CSS in Vite

Install lightningcss and browserslist, then configure both CSS transformation and minification.

sh
npm install -D lightningcss browserslist
ts
// vite.config.ts
import { defineConfig } from 'vite'
import browserslist from 'browserslist'
import { browserslistToTargets } from 'lightningcss'

export default defineConfig({
  css: {
    transformer: 'lightningcss',
    lightningcss: {
      targets: browserslistToTargets(browserslist('>= 0.25%'))
    }
  },
  build: {
    cssMinify: 'lightningcss'
  }
})

Merge these options into your existing Vite config. Adjust the browser query for the browsers your project supports.

Vite documentation · Lightning CSS browser targets.