Nuxt 3: SSR dan generasi statis, panduan lengkap

Menguasai SSR dan generasi statis dengan Nuxt 3. Dari useFetch hingga route rules, pelajari cara mengoptimalkan performa aplikasi Vue.js.

Ilustrasi yang menunjukkan rendering sisi server dan generasi statis dengan Nuxt 3 dan Vue.js

Nuxt 3 mengubah cara aplikasi Vue.js dibangun dengan menyediakan beberapa mode rendering yang sesuai untuk berbagai kasus penggunaan. Dari Server-Side Rendering (SSR) hingga generasi statis dan rendering hibrida, framework ini memberikan fleksibilitas luar biasa untuk mengoptimalkan performa dan SEO.

Prasyarat

Tutorial ini mengasumsikan pengetahuan dasar tentang Vue 3 dan Composition API. Keakraban dengan konsep rendering sisi server akan membantu, tetapi tidak wajib, karena dasar-dasarnya dijelaskan di sepanjang panduan.

Memahami mode rendering Nuxt 3

Sebelum menyelami kode, penting untuk memahami perbedaan antara mode rendering yang tersedia. Setiap mode menjawab kebutuhan spesifik dalam hal performa, SEO, dan pengalaman pengguna.

SSR (Server-Side Rendering) menghasilkan HTML di server untuk setiap permintaan. Generasi statis (SSG) membuat semua halaman terlebih dahulu pada saat build. Mode hibrida memungkinkan kombinasi pendekatan ini per halaman.

nuxt.config.tstypescript
// Konfigurasi berbagai mode rendering
export default defineNuxtConfig({
  // SSR aktif secara default (direkomendasikan untuk SEO)
  ssr: true,

  // Generasi statis: pra-render semua halaman
  // Gunakan 'npm run generate' untuk build
  // target: 'static', // Sintaks Nuxt 2

  // Mode hibrida: dapat dikonfigurasi per route
  routeRules: {
    // Halaman beranda: di-pra-render dan disimpan dalam cache
    '/': { prerender: true },
    // Blog: generasi statis
    '/blog/**': { prerender: true },
    // Dashboard: rendering hanya di sisi klien
    '/dashboard/**': { ssr: false },
    // API: tanpa pra-rendering
    '/api/**': { prerender: false }
  }
})

Konfigurasi ini menunjukkan kekuatan mode hibrida: setiap bagian aplikasi menggunakan mode rendering yang paling sesuai dengan kebutuhannya.

Pengambilan data dengan useFetch dan useAsyncData

Nuxt 3 menyediakan dua composable utama untuk mengambil data secara isomorfik. Composable ini bekerja baik di sisi server maupun klien, dengan manajemen hidrasi otomatis.

useFetch adalah wrapper di sekitar useAsyncData yang menyederhanakan panggilan HTTP. useAsyncData memberikan kontrol lebih untuk kasus penggunaan tingkat lanjut.

vue
<script setup lang="ts">
// pages/blog/[slug].vue
// Halaman detail artikel dengan useFetch

// Mengambil parameter route
const route = useRoute()

// useFetch: pengambilan data otomatis
// Data diambil di server lalu dihidrasi di klien
const { data: article, pending, error } = await useFetch(
  `/api/articles/${route.params.slug}`,
  {
    // Kunci unik untuk cache dan deduplikasi
    key: `article-${route.params.slug}`,
    // Mengubah data jika perlu
    transform: (response) => response.data,
    // Opsi cache
    getCachedData: (key) => {
      // Memeriksa apakah data ada di cache
      const nuxtApp = useNuxtApp()
      return nuxtApp.payload.data[key]
    }
  }
)

// Penanganan error dengan navigasi
if (error.value) {
  throw createError({
    statusCode: 404,
    message: 'Artikel tidak ditemukan'
  })
}
</script>

<template>
  <div>
    <div v-if="pending" class="loading">
      Memuat artikel...
    </div>
    <article v-else-if="article">
      <h1>{{ article.title }}</h1>
      <div v-html="article.content" />
    </article>
  </div>
</template>

Untuk kasus yang membutuhkan kontrol lebih, useAsyncData memungkinkan eksekusi fungsi asinkron apa pun.

vue
<script setup lang="ts">
// pages/products/index.vue
// Daftar produk dengan useAsyncData dan filter

const route = useRoute()

// useAsyncData: kontrol penuh atas logika fetching
const { data: products, refresh } = await useAsyncData(
  'products-list',
  async () => {
    // Mengambil dari beberapa sumber jika perlu
    const [productsResponse, categoriesResponse] = await Promise.all([
      $fetch('/api/products', {
        query: {
          category: route.query.category,
          sort: route.query.sort || 'date'
        }
      }),
      $fetch('/api/categories')
    ])

    // Menggabungkan dan mengubah data
    return {
      products: productsResponse.data,
      categories: categoriesResponse.data,
      total: productsResponse.meta.total
    }
  },
  {
    // Refresh ketika query params berubah
    watch: [() => route.query]
  }
)

// Fungsi refresh manual
const updateFilters = async (newCategory: string) => {
  await navigateTo({
    query: { ...route.query, category: newCategory }
  })
}
</script>

Composable ini mencegah double fetching: data yang diperoleh di server diserialisasi ke dalam payload HTML dan digunakan kembali selama hidrasi di sisi klien.

Menyesuaikan SSR dengan server hooks

SSR di Nuxt 3 dapat disesuaikan melalui server hooks. Hook ini memungkinkan intervensi pada berbagai tahap siklus rendering untuk memodifikasi perilaku default.

server/plugins/render-hooks.tstypescript
// Plugin server untuk menyesuaikan rendering SSR

export default defineNitroPlugin((nitroApp) => {
  // Hook yang dijalankan sebelum rendering setiap halaman
  nitroApp.hooks.hook('render:html', (html, { event }) => {
    // Menyuntikkan script atau metadata
    html.head.push(`
      <script>
        // Analitik atau konfigurasi global
        window.__APP_CONFIG__ = {
          environment: '${process.env.NODE_ENV}',
          apiUrl: '${process.env.API_URL}'
        }
      </script>
    `)
  })

  // Hook untuk pengelolaan cache rendering
  nitroApp.hooks.hook('render:response', (response, { event }) => {
    // Menambahkan header cache kustom
    const path = event.path

    if (path.startsWith('/blog/')) {
      // Cache panjang untuk artikel blog
      response.headers['Cache-Control'] = 'public, max-age=3600, s-maxage=86400'
    } else if (path.startsWith('/api/')) {
      // Tanpa cache untuk API
      response.headers['Cache-Control'] = 'no-store'
    }
  })
})
Performa SSR

Hook render:response ideal untuk menerapkan strategi cache HTTP. Menggabungkan SSR dengan CDN yang menghormati header Cache-Control memungkinkan penyajian halaman pra-render sambil tetap dapat melakukan invalidasi.

Generasi statis dengan nuxt generate

Generasi statis membangun semua halaman terlebih dahulu pada saat build. Pendekatan ini ideal untuk situs dengan konten stabil seperti blog, dokumentasi, atau situs marketing.

Untuk route dinamis, Nuxt perlu mengetahui semua URL yang akan dihasilkan. Hook prerender:routes memungkinkan pendefinisian route ini secara programatik.

nuxt.config.tstypescript
// Konfigurasi lengkap untuk generasi statis

export default defineNuxtConfig({
  // Mengaktifkan generasi statis
  nitro: {
    prerender: {
      // Mengaktifkan crawling link otomatis
      crawlLinks: true,
      // Route yang harus selalu disertakan
      routes: ['/', '/about', '/contact'],
      // Mengabaikan route tertentu
      ignore: ['/admin', '/api']
    }
  },

  hooks: {
    // Hook untuk menghasilkan route dinamis
    async 'prerender:routes'(ctx) {
      // Mengambil artikel dari API atau DB
      const articles = await fetch('https://api.example.com/articles')
        .then(res => res.json())

      // Menambahkan route artikel
      for (const article of articles) {
        ctx.routes.add(`/blog/${article.slug}`)
      }

      // Mengambil kategori
      const categories = await fetch('https://api.example.com/categories')
        .then(res => res.json())

      for (const category of categories) {
        ctx.routes.add(`/category/${category.slug}`)
      }
    }
  }
})

Untuk proyek dengan banyak halaman, crawler otomatis mungkin tidak cukup. Berikut pendekatan yang lebih kuat dengan file konfigurasi terpisah.

server/utils/generate-routes.tstypescript
// Utilitas untuk menghasilkan daftar route dinamis

import { prisma } from './prisma'

export async function getAllStaticRoutes(): Promise<string[]> {
  const routes: string[] = []

  // Artikel blog
  const articles = await prisma.article.findMany({
    where: { published: true },
    select: { slug: true, category: { select: { slug: true } } }
  })

  for (const article of articles) {
    routes.push(`/blog/${article.category.slug}/${article.slug}`)
  }

  // Halaman produk
  const products = await prisma.product.findMany({
    where: { active: true },
    select: { slug: true }
  })

  for (const product of products) {
    routes.push(`/products/${product.slug}`)
  }

  // Halaman tag
  const tags = await prisma.tag.findMany({
    select: { slug: true }
  })

  for (const tag of tags) {
    routes.push(`/tags/${tag.slug}`)
  }

  return routes
}

Siap menguasai wawancara Vue.js / Nuxt.js Anda?

Berlatih dengan simulator interaktif, flashcards, dan tes teknis kami.

Rendering hibrida dengan routeRules

Rendering hibrida adalah fitur unggulan Nuxt 3. Fitur ini memungkinkan pendefinisian aturan rendering yang berbeda per route, menggabungkan keunggulan SSR dan SSG.

nuxt.config.tstypescript
// Konfigurasi lanjutan rendering hibrida

export default defineNuxtConfig({
  routeRules: {
    // Halaman marketing: di-pra-render dan disimpan dalam cache jangka panjang
    '/': { prerender: true },
    '/pricing': { prerender: true },
    '/features/**': { prerender: true },

    // Blog: ISR (Incremental Static Regeneration)
    // Revalidasi setiap jam
    '/blog/**': {
      isr: 3600,
      prerender: true
    },

    // Dokumentasi: cache CDN dengan revalidasi
    '/docs/**': {
      swr: 86400, // Stale-while-revalidate
      prerender: true
    },

    // E-commerce: SSR dengan cache pendek
    '/products/**': {
      ssr: true,
      cache: {
        maxAge: 60,
        staleMaxAge: 300
      }
    },

    // Keranjang dan checkout: hanya sisi klien
    '/cart': { ssr: false },
    '/checkout/**': { ssr: false },

    // Dashboard: mode SPA
    '/dashboard/**': {
      ssr: false,
      // Menonaktifkan pra-rendering
      prerender: false
    },

    // Route API: tanpa cache secara default
    '/api/**': {
      cors: true,
      headers: {
        'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE'
      }
    }
  }
})

Konfigurasi ini menggambarkan arsitektur khas aplikasi modern: halaman publik dioptimalkan untuk SEO dengan SSG, sementara bagian interaktif menggunakan rendering sisi klien.

Optimasi performa dengan caching data

Selain caching halaman, Nuxt 3 memungkinkan caching data yang diambil. Strategi ini mengurangi beban pada API dan meningkatkan waktu respons.

server/api/articles/[slug].get.tstypescript
// Endpoint API dengan caching data

import { getArticleBySlug } from '~/server/utils/articles'

export default defineCachedEventHandler(
  async (event) => {
    const slug = getRouterParam(event, 'slug')

    if (!slug) {
      throw createError({
        statusCode: 400,
        message: 'Slug tidak ada'
      })
    }

    const article = await getArticleBySlug(slug)

    if (!article) {
      throw createError({
        statusCode: 404,
        message: 'Artikel tidak ditemukan'
      })
    }

    return article
  },
  {
    // Kunci cache berdasarkan slug
    getKey: (event) => `article-${getRouterParam(event, 'slug')}`,
    // Durasi cache: 1 jam
    maxAge: 3600,
    // Stale-while-revalidate: menyajikan cache kedaluwarsa selama pembaruan
    staleMaxAge: 7200,
    // Invalidasi berbasis tag
    tags: ['articles']
  }
)

Untuk menginvalidasi cache saat konten berubah, Nuxt menyediakan sistem tag.

server/api/articles/[slug].put.tstypescript
// Pembaruan artikel dengan invalidasi cache

import { updateArticle } from '~/server/utils/articles'

export default defineEventHandler(async (event) => {
  const slug = getRouterParam(event, 'slug')
  const body = await readBody(event)

  // Memperbarui artikel
  const article = await updateArticle(slug, body)

  // Menginvalidasi cache untuk artikel ini
  await useStorage('cache').removeItem(`nitro:handlers:article-${slug}`)

  // Atau invalidasi berbasis tag (semua artikel)
  // await useStorage('cache').clear('articles')

  return article
})
Cache terdistribusi

Di produksi dengan beberapa instance, cache dalam memori tidak mencukupi. Disarankan untuk mengonfigurasi Redis atau sistem terdistribusi lainnya melalui konfigurasi Nitro guna memastikan konsistensi antar instance.

Pengelolaan SEO dan metadata

SSR memungkinkan optimasi SEO dengan menghasilkan metadata di sisi server. Nuxt 3 menawarkan beberapa pendekatan untuk mengelola meta tag secara dinamis.

vue
<script setup lang="ts">
// pages/blog/[slug].vue
// Halaman blog dengan SEO yang dioptimalkan

const route = useRoute()

const { data: article } = await useFetch(`/api/articles/${route.params.slug}`)

// Konfigurasi SEO dinamis berdasarkan artikel
useSeoMeta({
  title: article.value?.title,
  description: article.value?.excerpt,
  ogTitle: article.value?.title,
  ogDescription: article.value?.excerpt,
  ogImage: article.value?.coverImage,
  ogType: 'article',
  twitterCard: 'summary_large_image',
  twitterTitle: article.value?.title,
  twitterDescription: article.value?.excerpt,
  twitterImage: article.value?.coverImage
})

// Data terstruktur untuk Google
useHead({
  script: [
    {
      type: 'application/ld+json',
      innerHTML: JSON.stringify({
        '@context': 'https://schema.org',
        '@type': 'Article',
        headline: article.value?.title,
        description: article.value?.excerpt,
        image: article.value?.coverImage,
        datePublished: article.value?.publishedAt,
        dateModified: article.value?.updatedAt,
        author: {
          '@type': 'Organization',
          name: 'SharpSkill'
        }
      })
    }
  ]
})
</script>

Untuk halaman statis, metadata dapat didefinisikan langsung di komponen.

vue
<script setup lang="ts">
// pages/about.vue
// Halaman statis dengan SEO

definePageMeta({
  title: 'Tentang'
})

useSeoMeta({
  title: 'Tentang SharpSkill | Persiapan wawancara teknis',
  description: 'Mengenal SharpSkill, platform persiapan wawancara teknis. Misinya: membantu para pengembang berhasil dalam wawancara teknis mereka.',
  ogTitle: 'Tentang SharpSkill',
  ogDescription: 'Platform persiapan wawancara teknis',
  ogImage: '/images/og-about.webp'
})
</script>

Penerapan dan pertimbangan produksi

Pilihan penerapan tergantung pada mode rendering yang digunakan. Berikut opsi utama dan konfigurasinya.

nuxt.config.tstypescript
// Konfigurasi untuk berbagai lingkungan penerapan

export default defineNuxtConfig({
  nitro: {
    // Preset sesuai platform target
    // preset: 'vercel', // Vercel
    // preset: 'netlify', // Netlify
    // preset: 'cloudflare-pages', // Cloudflare
    // preset: 'node-server', // Node.js klasik

    // Konfigurasi untuk Node.js di produksi
    preset: 'node-server',

    // Kompresi respons
    compressPublicAssets: true,

    // Konfigurasi penyimpanan cache
    storage: {
      cache: {
        driver: 'redis',
        url: process.env.REDIS_URL
      }
    }
  },

  // Variabel lingkungan runtime
  runtimeConfig: {
    // Rahasia (tidak diekspos ke klien)
    apiSecret: process.env.API_SECRET,
    // Konfigurasi publik
    public: {
      apiBase: process.env.NUXT_PUBLIC_API_BASE || '/api'
    }
  }
})

Untuk penerapan statis, perintah npm run generate membuat folder .output/public yang siap diterapkan di host file statis mana pun.

bash
# Generasi statis
npm run generate

# Konten .output/public dapat diterapkan ke:
# - Vercel (deteksi otomatis)
# - Netlify (konfigurasi otomatis)
# - GitHub Pages
# - S3 + CloudFront
# - CDN atau server file statis apa pun

Mulai berlatih!

Uji pengetahuan Anda dengan simulator wawancara dan tes teknis kami.

Kesimpulan

Nuxt 3 menawarkan fleksibilitas luar biasa untuk merender aplikasi Vue.js. Pilihan antara SSR, SSG, dan rendering hibrida tergantung pada kebutuhan spesifik setiap proyek.

Poin-poin penting:

SSR: ideal untuk konten dinamis yang membutuhkan SEO yang baik (e-commerce, situs berita)

SSG: sempurna untuk konten stabil (blog, dokumentasi, situs marketing)

Hibrida: pendekatan terbaik untuk aplikasi kompleks dengan kebutuhan beragam

useFetch/useAsyncData: hidrasi otomatis dan pengelolaan cache

routeRules: konfigurasi terperinci untuk perilaku setiap route

Caching: berbagai strategi untuk mengoptimalkan performa di produksi

Menggabungkan rendering hibrida dengan strategi caching yang matang memungkinkan pembangunan aplikasi berkinerja tinggi yang dioptimalkan untuk SEO sambil tetap mempertahankan interaktivitas Single Page Applications.

Tag

#nuxt 3
#vue.js
#ssr
#generasi statis
#performa web

Bagikan

Artikel terkait