Chuyển đến nội dung chính

Lesson 10: Data Fetching & SSR

useFetch, useAsyncData, $fetch. SSR vs CSR vs SSG vs ISR. Hydration, payload optimization. useLazyFetch, refresh/execute. Error handling, loading states.

💻 Programming — Lesson 10 Lesson 10: Data Fetching & SSR

Vue.js & Nuxt: From Basics to Advanced

Part 3: Nuxt 3 Fundamentals

xdev.asia

1. useFetch

<script setup lang="ts">
interface Post {
  id: string
  title: string
  content: string
}

// Basic usage — SSR-friendly
const { data: posts, status, error, refresh } = await useFetch<Post[]>('/api/posts')

// With options
const { data: post } = await useFetch<Post>(`/api/posts/${slug}`, {
  pick: ['title', 'content'], // Only transfer these fields
  transform: (data) => ({
    ...data,
    title: data.title.toUpperCase(),
  }),
})

// With query params
const page = ref(1)
const { data } = await useFetch('/api/posts', {
  query: { page, limit: 10 }, // Reactive — auto-refetch
  watch: [page], // Watch for changes
})
</script>

<template>
  <div v-if="status === 'pending'">Loading...</div>
  <div v-else-if="error">Error: {{ error.message }}</div>
  <div v-else>
    <div v-for="post in posts" :key="post.id">{{ post.title }}</div>
    <button @click="refresh()">Refresh</button>
  </div>
</template>

2. useAsyncData

<script setup lang="ts">
// Khi cần custom fetching logic
const { data: user } = await useAsyncData('user', () => {
  return $fetch('/api/user/me')
})

// Multiple parallel fetches
const [{ data: posts }, { data: categories }] = await Promise.all([
  useFetch('/api/posts'),
  useFetch('/api/categories'),
])

// Cached key — dedup requests
const { data } = await useAsyncData(
  `post-${slug}`, // Unique key
  () => $fetch(`/api/posts/${slug}`),
  { dedupe: 'cancel' } // Cancel previous request
)
</script>

3. Lazy Fetching

<script setup lang="ts">
// useLazyFetch — không block navigation, render ngay
const { data, pending } = useLazyFetch('/api/posts')

// useLazyAsyncData
const { data: comments, pending: loadingComments } = useLazyAsyncData(
  'comments',
  () => $fetch(`/api/posts/${slug}/comments`)
)
</script>

<template>
  <div>
    <div v-if="pending">Loading posts...</div>
    <div v-else v-for="post in data" :key="post.id">{{ post.title }}</div>

    <!-- Comments load separately -->
    <div v-if="loadingComments">Loading comments...</div>
    <div v-else v-for="c in comments" :key="c.id">{{ c.text }}</div>
  </div>
</template>

4. Rendering Modes

ModeDescriptionConfig
SSRServer-side rendering (default)ssr: true
CSRClient-side onlyssr: false
SSGStatic site generationnpx nuxi generate
ISRIncremental static + revalidationRoute rules
SWRStale-while-revalidateRoute rules
// nuxt.config.ts — Hybrid rendering
export default defineNuxtConfig({
  routeRules: {
    '/': { prerender: true },                    // SSG
    '/blog/**': { isr: 3600 },                   // ISR — revalidate 1h
    '/dashboard/**': { ssr: false },             // CSR only
    '/api/**': { cors: true },                   // API CORS
    '/admin/**': { ssr: false, robots: false },  // CSR + no index
  },
})

5. $fetch — Direct HTTP

// $fetch — dùng trong event handlers, không trong setup
async function deletePost(id: string) {
  await $fetch(`/api/posts/${id}`, {
    method: 'DELETE',
  })
  await refreshNuxtData('posts') // Refresh cached data
}

// POST
const result = await $fetch('/api/posts', {
  method: 'POST',
  body: { title: 'New Post', content: '...' },
})

Next article: Nitro Server Engine & API Routes — server routes, middleware, database.