Overview

View source
Nuxt data-fetching fundamentals and the core concepts and architecture of the Movk Nuxt API system.

Nuxt Data Fetching Fundamentals

Nuxt provides three data-fetching approaches. Understanding the differences helps you choose the right Movk Nuxt wrapper.

  • $fetch: The low-level HTTP client, based on ofetch, usable on both server and client.
    // Called on user interaction — use $fetch
    async function handleSubmit() {
      const result = await $fetch('/api/login', {
        method: 'POST',
        body: { username, password }
      })
    }
    
  • useFetch: A syntactic sugar wrapper around useAsyncData + $fetch.
    // Fetch data on page load — use useFetch
    const { data: posts, status, error } = await useFetch('/api/posts')
    
  • useAsyncData: The most flexible data-fetching composable.
    // Aggregate multiple endpoints — use useAsyncData
    const { data } = await useAsyncData('dashboard', async () => {
      const [users, stats] = await Promise.all([
        $fetch('/api/users'),
        $fetch('/api/stats')
      ])
      return { users, stats }
    })
    

Key Parameters Explained

Three core parameters of useFetch (and useApiFetch) determine when and how requests are executed.

server

Controls whether data fetching is executed on the server. Defaults to true.

Behavior comparison:

  • server: true (default) — During SSR the request is made on the server; data is transferred to the client via payload and not re-fetched on the client.
  • server: false — Executed on the client only. On first load, data starts fetching after hydration completes; data is initially null.

Use cases:

  • server: true — SEO-relevant data, content required on first render.
  • server: false — Non-SEO-sensitive data (user comments, personalized recommendations, analytics), reducing server load.
// Article list needs SEO — server: true (default)
const { data: posts } = await useApiFetch<Post[]>('/posts')

// Comment data doesn't need SSR — server: false
const { data: comments } = useApiFetch('/comments', {
  server: false,
  lazy: true,
})
server: false is typically paired with lazy: true, which is exactly the preset behavior of useClientApiFetch.

lazy

Controls whether client-side navigation is blocked. Defaults to false.

Behavior comparison:

  • lazy: false (default) — Blocks navigation via Vue Suspense, waiting for data before completing the route transition. The user never sees a loading state.
  • lazy: true — Does not block navigation; the page displays immediately while data loads in the background. You must handle the loading state manually.

Use cases:

  • lazy: false — Core page data without which the page cannot render correctly.
  • lazy: true — Secondary data, non-above-the-fold data, scenarios where a skeleton screen can be shown first.
// Core data — block navigation and wait for data (default)
const { data: article } = await useApiFetch<Article>(`/articles/${id}`)

// Secondary data — don't block navigation, load in background
const { data: related, status } = useLazyApiFetch<Article[]>(
  `/articles/${id}/related`
)
<template>
  <!-- Handle loading state when using lazy -->
  <div v-if="status === 'pending'">
    <USkeleton class="h-20 w-full" />
  </div>
  <RelatedArticles v-else :articles="related" />
</template>
During SSR, lazy behaves nearly the same as non-lazy (both wait for data); the difference primarily applies during client-side navigation.

immediate

Controls whether the request fires immediately when the composable is initialized. Defaults to true.

Behavior comparison:

  • immediate: true (default) — Triggers the request on creation.
  • immediate: false — No automatic request; status is idle. You must call execute() manually.

Use cases:

  • immediate: true — Data needed as soon as the page loads.
  • immediate: false — Requests that should only be triggered after a user action.
// Pre-define a form-submit request, don't auto-execute
const { data, execute, status, error } = useApiFetch('/login', {
  method: 'POST',
  body: credentials,
  immediate: false,
  lazy: true,
})

// Trigger manually when the user clicks the login button
async function handleLogin() {
  await execute()
  if (!error.value) {
    navigateTo('/dashboard')
  }
}
immediate: false is commonly paired with lazy: true. If only immediate: false is set without lazy: true, Suspense will still wait (even though the request never fires), which may cause the page to hang.

Movk Nuxt API System

Movk Nuxt provides an API client factory based on $fetch.create() (the $api plugin) and a set of composables that wrap useFetch, adding unified auth, business code checking, Toast notifications and other enhancements on top of Nuxt's native data-fetching capabilities.

nuxt.config.ts (movk.api config)
    |
    v
api.factory.ts plugin — creates $api instance
    |
    v
Interceptor chain
├── onRequest:       Auth injection -> debug logging -> movk:api:request hook
├── onResponse:      Business check -> data unwrap -> movk:api:response hook -> Toast
└── onResponseError: 401 handling -> movk:api:error hook -> Toast
    |
    v
Composables
├── useApiFetch           useFetch + $api (SSR/CSR universal)
├── useLazyApiFetch       lazy: true preset
├── useClientApiFetch     server: false, lazy: true preset
├── useUploadWithProgress XHR upload + progress
└── useDownloadWithProgress fetch + ReadableStream download + progress

Multi-Endpoint Support

Multiple API endpoints can be configured, each with its own baseURL, auth, Toast and response rules. Switch endpoints with the endpoint option or $api.use().

Learn about endpoint configuration options

Automatic Authentication

Integrates with nuxt-auth-utils. When enabled, each request automatically extracts the token from the session and injects it into the request headers. Supports nested paths (e.g. user.accessToken) and multiple token formats (Bearer, Basic, Custom).

Learn about authentication configuration options

Business Status Code Check

The $api interceptor automatically checks whether the response code is in successCodes, throwing an ApiError and showing a Toast on business failure.

See interactive examples for business validation and skipBusinessCheck

Data Unwrapping

The $api interceptor automatically extracts the dataKey field (default data) from the response; the generic T directly maps to the business data type.

See interactive examples for unwrapping and skipUnwrap

Toast Notifications

Success/failure Toasts are shown automatically. Supports four levels of configuration priority: request-level > endpoint-level > global-level > built-in defaults. Can be disabled or customized at any level.

Learn about Toast configuration options

401 Unauthorized Handling

When an HTTP 401 is received, the default behavior clears the session and redirects to the login page. This can be intercepted via API Hooks to implement custom logic (e.g. token refresh).

Learn about 401 handling configuration options
Copyright © 2025 - 2026 YiXuan - MIT License