Skip to main content
The Nuxt client provides native integration with Nuxt 3, offering composables like useFetch, useAsyncData, and SSR support out of the box.

Installation

npm install @hey-api/client-nuxt

Basic Usage

1

Create a client instance

import { createClient } from '@hey-api/client-nuxt';

export const client = createClient({
  baseURL: 'https://api.example.com',
});
2

Use with $fetch

<script setup>
import { client } from '~/lib/api-client';

const { data, error } = await client.get({
  url: '/users',
  composable: '$fetch',
});
</script>

<template>
  <div>
    <div v-if="error">Error: {{ error }}</div>
    <div v-else>
      <div v-for="user in data" :key="user.id">
        {{ user.name }}
      </div>
    </div>
  </div>
</template>
3

Use with useFetch

<script setup>
import { client } from '~/lib/api-client';

const { data, error, pending, refresh } = await client.get({
  url: '/users',
  composable: 'useFetch',
});
</script>

<template>
  <div>
    <div v-if="pending">Loading...</div>
    <div v-else-if="error">Error: {{ error }}</div>
    <div v-else>
      <div v-for="user in data" :key="user.id">
        {{ user.name }}
      </div>
      <button @click="refresh()">Refresh</button>
    </div>
  </div>
</template>

Configuration

The Nuxt client extends Nuxt’s fetch configuration:

Client Options

import { createClient } from '@hey-api/client-nuxt';

const client = createClient({
  // Base URL for all requests
  baseURL: 'https://api.example.com',

  // Default headers
  headers: {
    'Content-Type': 'application/json',
    'X-API-Key': 'your-api-key',
  },

  // Query parameters (can be refs)
  query: {
    version: 'v1',
  },

  // Authentication
  auth: async (auth) => getToken(),

  // Body serialization
  bodySerializer: (body) => JSON.stringify(body),

  // Query serialization
  querySerializer: {
    array: { style: 'form', explode: true },
    object: { style: 'deepObject', explode: true },
  },

  // Request validator
  requestValidator: async (data) => validateRequest(data),

  // Response transformer
  responseTransformer: async (data) => transformResponse(data),

  // Response validator
  responseValidator: async (data) => validateResponse(data),

  // Standard fetch options
  credentials: 'include',
  mode: 'cors',
  cache: 'default',

  // Nuxt-specific options
  retry: 1,
  retryDelay: 500,
  retryStatusCodes: [408, 409, 425, 429, 500, 502, 503, 504],
  timeout: 10000,
});

Composables

The Nuxt client supports all Nuxt composables:

$fetch

Direct fetch without reactivity:
const data = await client.get({
  url: '/users',
  composable: '$fetch',
});

console.log(data);

useFetch

Reactive data fetching with SSR support:
<script setup>
const { data, pending, error, refresh } = await client.get({
  url: '/users/{id}',
  path: { id: route.params.id },
  composable: 'useFetch',
});
</script>

useAsyncData

Custom async data handling:
<script setup>
const { data, pending, error, refresh } = await client.get({
  url: '/users',
  composable: 'useAsyncData',
  key: 'users-list',
  asyncDataOptions: {
    server: true,
    lazy: false,
    default: () => [],
  },
});
</script>

useLazyFetch

Lazy loading with useFetch:
<script setup>
const { data, pending, error } = await client.get({
  url: '/users',
  composable: 'useLazyFetch',
});
</script>

<template>
  <div>
    <div v-if="pending">Loading...</div>
    <div v-else-if="data">
      <!-- Render data -->
    </div>
  </div>
</template>

useLazyAsyncData

Lazy loading with useAsyncData:
<script setup>
const { data, pending } = await client.get({
  url: '/users',
  composable: 'useLazyAsyncData',
  key: 'users',
});
</script>

HTTP Methods

<script setup>
const { data, error } = await client.get({
  url: '/users',
  query: {
    page: 1,
    limit: 10,
  },
  composable: 'useFetch',
});
</script>

Reactive Parameters

Use Vue refs for reactive query parameters:
<script setup>
import { ref } from 'vue';

const page = ref(1);
const limit = ref(10);

const { data, refresh } = await client.get({
  url: '/users',
  query: {
    page,
    limit,
  },
  composable: 'useFetch',
});

// When page changes, the request automatically updates
function nextPage() {
  page.value++;
}
</script>

<template>
  <div>
    <UsersList :users="data" />
    <button @click="nextPage">Next Page</button>
  </div>
</template>

Authentication

import { useCookie } from '#app';

const client = createClient({
  baseURL: 'https://api.example.com',
  auth: async (auth) => {
    if (auth.scheme === 'bearer') {
      const token = useCookie('access_token');
      return token.value;
    }
  },
});

Token from Composable

<script setup>
import { useAuth } from '~/composables/useAuth';

const { token } = useAuth();

const { data } = await client.get({
  url: '/protected',
  headers: {
    Authorization: `Bearer ${token.value}`,
  },
  composable: 'useFetch',
});
</script>

Per-Request Security

const { data } = await client.get({
  url: '/protected',
  security: [
    {
      type: 'apiKey',
      in: 'header',
      name: 'X-API-Key',
    },
  ],
  composable: 'useFetch',
});

Server-Sent Events

Stream real-time data:
<script setup>
import { ref, onUnmounted } from 'vue';

const events = ref([]);
let stream;

onMounted(async () => {
  stream = await client.sse.get({
    url: '/events',
    onSseEvent: (event) => {
      events.value.push(event.data);
    },
    onSseError: (error) => {
      console.error('Stream error:', error);
    },
  });
});

onUnmounted(() => {
  stream?.close();
});
</script>

<template>
  <div>
    <div v-for="(event, i) in events" :key="i">
      {{ event }}
    </div>
  </div>
</template>

Caching and Refresh

Manual Refresh

<script setup>
const { data, refresh } = await client.get({
  url: '/users',
  composable: 'useFetch',
});

const refreshUsers = () => refresh();
</script>

<template>
  <div>
    <UsersList :users="data" />
    <button @click="refreshUsers">Refresh</button>
  </div>
</template>

Auto Refresh

<script setup>
const { data } = await client.get({
  url: '/stats',
  composable: 'useFetch',
  asyncDataOptions: {
    watch: [route.params.id],
  },
});
</script>

Cache Keys

<script setup>
const userId = route.params.id;

const { data } = await client.get({
  url: '/users/{id}',
  path: { id: userId },
  composable: 'useFetch',
  key: `user-${userId}`,
});
</script>

SSR Support

Server-Side Rendering

<script setup>
// This will run on the server during SSR
const { data } = await client.get({
  url: '/users',
  composable: 'useFetch',
  asyncDataOptions: {
    server: true, // Default
  },
});
</script>

Client-Only Fetching

<script setup>
const { data } = await client.get({
  url: '/users',
  composable: 'useFetch',
  asyncDataOptions: {
    server: false, // Skip SSR
  },
});
</script>

Error Handling

<script setup>
const { data, error, status } = await client.get({
  url: '/users',
  composable: 'useFetch',
});

const errorMessage = computed(() => {
  if (error.value) {
    return `Error ${status.value}: ${error.value.message}`;
  }
  return null;
});
</script>

<template>
  <div>
    <div v-if="errorMessage" class="error">
      {{ errorMessage }}
    </div>
    <div v-else>
      <!-- Render data -->
    </div>
  </div>
</template>

Advanced Examples

Parallel Requests

<script setup>
const [usersResult, postsResult] = await Promise.all([
  client.get({ url: '/users', composable: 'useFetch' }),
  client.get({ url: '/posts', composable: 'useFetch' }),
]);

const users = usersResult.data;
const posts = postsResult.data;
</script>

Dependent Requests

<script setup>
const { data: user } = await client.get({
  url: '/users/{id}',
  path: { id: route.params.id },
  composable: 'useFetch',
});

const { data: posts } = await client.get({
  url: '/users/{id}/posts',
  path: { id: user.value.id },
  composable: 'useFetch',
  asyncDataOptions: {
    immediate: false,
  },
});

watch(user, (newUser) => {
  if (newUser) {
    posts.refresh();
  }
});
</script>

Optimistic Updates

<script setup>
const { data: users, refresh } = await client.get({
  url: '/users',
  composable: 'useFetch',
});

const createUser = async (newUser) => {
  // Optimistic update
  users.value = [...users.value, { ...newUser, id: Date.now() }];

  try {
    const { data } = await client.post({
      url: '/users',
      body: newUser,
      composable: '$fetch',
    });
    
    // Refresh to get actual data
    await refresh();
  } catch (error) {
    // Revert on error
    await refresh();
  }
};
</script>

TypeScript Types

import type {
  Client,
  Config,
  RequestOptions,
  RequestResult,
  Composable,
} from '@hey-api/client-nuxt';

// Type-safe client wrapper
function createApiClient(config: Config): Client {
  return createClient({
    ...config,
    baseURL: useRuntimeConfig().public.apiUrl,
  });
}

// Typed composable
type UsersResponse = RequestResult<'useFetch', User[], Error>;

const users: UsersResponse = await client.get({
  url: '/users',
  composable: 'useFetch',
});

Best Practices

Choose the right composable for your use case:
  • $fetch for one-off requests
  • useFetch for reactive SSR data
  • useAsyncData for custom async logic
  • useLazyFetch for deferred loading
Use refs for parameters that should trigger re-fetching:
<script setup>
const search = ref('');

const { data } = await client.get({
  url: '/users',
  query: { search }, // Reactive!
  composable: 'useFetch',
});
</script>
Use unique keys for caching:
<script setup>
const { data } = await client.get({
  url: '/users/{id}',
  path: { id },
  key: `user-${id}`,
  composable: 'useFetch',
});
</script>
Always show loading indicators:
<template>
  <div v-if="pending">Loading...</div>
  <div v-else-if="error">Error: {{ error }}</div>
  <div v-else>
    <!-- Data -->
  </div>
</template>

Next Steps

Fetch Client

Learn about the underlying Fetch client

Composables

Explore Nuxt composables

SSR Support

Configure server-side rendering

Authentication

Set up API authentication

Build docs developers (and LLMs) love