核心修練
CoreSSR Ready

資料獲取

Nuxt 4 提供了強大的資料獲取組合式函數 (Composables),完美解決了 SSR 環境下的重複請求 (Double Fetching) 與水合不一致 (Hydration Mismatch) 問題。

為什麼不能直接用 fetch?

在傳統的 Vue SPA 中,我們習慣在 onMounted 鉤子中呼叫 API。 但在 Nuxt 的 SSR (伺服器端渲染) 模式下,這種做法會帶來兩個嚴重問題:

Traditional Fetch

CLIENT-SIDE PATTERN

Server
onMounted()
Lifecycle hook ignored
Client
Re-fetching Data
Causes layout shift & delay

Nuxt useFetch

UNIVERSAL DATA FETCHING

Server
Prefetch & Cache
Data serialized into HTML
Client
Hydration
Instant restore from payload

Under the Hood

SSR Data Pipeline
API
1. Prefetch

Server 執行請求並等待結果

JSON
2. Serialize

資料嵌入 HTML <script>

DOM
3. Hydrate

前端直接還原狀態,零請求


基本用法

useFetch 是 Nuxt 提供的全能型工具,它自動處理了上述所有問題。你只需要一行程式碼,就能獲得響應式的資料與請求狀態。

app.vue
const { data, status, error, refresh } = await useFetch('/api/users')

// data: 回傳的資料 (Ref<T>)
// status: 請求狀態 'idle' | 'pending' | 'success' | 'error'
// error: 錯誤物件
// refresh: 重新執行請求的函式
data
回傳的資料結果,型別為 Ref<T>。若請求失敗則為 null。
status
目前的請求狀態字串:
'idle' | 'pending' | 'success' | 'error'
error
當請求失敗時的錯誤物件,包含 statusCode 與 message。
refresh()
一個函式,呼叫後會強制重新發送請求,常用於「重新整理」按鈕。

進階特性

Lazy Loading (非阻塞導航)

預設情況下,Nuxt 會等待所有 useFetch 完成後才切換頁面 (Blocking Navigation)。

若 API 回應較慢,使用者會感覺點擊後「沒反應」。啟用 lazy: true 後,頁面會立即切換,並在背景載入資料。這時你需要透過 status 來顯示 Loading 骨架屏。

<script setup>
// 啟用 lazy: true,不會阻塞頁面導航
const { data, status } = await useFetch('/api/posts', {
  lazy: true
})
</script>

<template>
  <!-- 透過 status 處理載入狀態 -->
  <div v-if="status === 'pending'">
    載入中...
  </div>
  <div v-else>
    {{ data }}
  </div>
</template>

自動監聽與參數更新

在製作搜尋、分頁功能時,我們希望當參數改變時自動重抓資料。

useFetch 會自動解包 (Unwrap) 傳入 query 的 ref。或者,你也可以使用 watch 選項來明確指定要監聽的變數。

const page = ref(1)
const search = ref('')

const { data } = await useFetch('/api/users', {
  // query 參數會自動解包 ref
  query: { 
    page, 
    q: search 
  },
  // 當 page 或 search 改變時,自動重新發送請求
  watch: [page, search]
})

useAsyncData 的使用時機

useFetch 其實是 useAsyncData 加上 $fetch 的語法糖。 但在某些情況下,你必須直接使用 useAsyncData

  • 整合第三方 SDK (Firebase, CMS Clients, GraphQL)。
  • 需要串聯多個請求後才回傳單一結果。
// 當你需要執行非 fetch 的異步操作 (例如 CMS SDK 或 GraphQL)
const { data } = await useAsyncData(
  'unique-key-for-caching', // [重要] 必須提供唯一的 key
  async () => {
    const cmsData = await myCmsClient.getPosts()
    return cmsData.filter(post => post.published)
  }
)
關於 Key 的重要性 在使用 useAsyncData 時,務必提供一個唯一的 key。Nuxt 依賴這個 key 在 Server 與 Client 之間傳遞資料快取 (Payload)。若 key 重複,可能導致資料汙染或水合錯誤。

$fetch vs useFetch

Nuxt 提供了兩個長得很像的工具,初學者常混淆。請記住以下原則:

useFetch

Composable (Setup Context)

頁面初始化資料進入頁面時就需要看到的資料 (SSR)。
自動去重 (Deduplication)避免 Server 與 Client 重複發送請求。
需要狀態管理需要 loading 狀態、錯誤處理、快取。

$fetch

Utility Function (Anywhere)

使用者互動觸發點擊按鈕送出表單、登入、加入購物車。
非元件環境在 Middleware、Pinia Actions 或純 JS 檔中使用。

實戰演練

以下範例展示了如何從 JSONPlaceholder API 獲取使用者列表。我們使用了 lazy 模式來優化載入體驗,並透過 transform 只保留前 4 筆資料。

Live Preview

使用者列表

LG
Leanne Graham
Sincere@april.biz
EH
Ervin Howell
Shanna@melissa.tv
CB
Clementine Bauch
Nathan@yesenia.net
PL
Patricia Lebsack
Julianne.OConner@kory.org
Core Logic
DataFetching.vue
type User = { id: number; name: string; email: string }

// [實戰範例] 結合 Lazy Loading 與資料轉換
const { data: users, status, error, refresh } = await useFetch<User[]>(
  'https://jsonplaceholder.typicode.com/users', 
  {
    lazy: true, // 非阻塞模式
    // [重點] 只取前 4 筆,減少前端處理負擔
    transform: (users) => users.slice(0, 4),
    // [重點] 設定唯一的 key,避免水合不一致
    key: 'users-list-demo'
  }
)
小技巧 試著點擊範例右上角的「重新整理」按鈕,觀察 Skeleton Loading 的效果。這就是 status === 'pending' 的實際應用。