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.

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.
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.
// 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.
<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.
<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.
// 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'
}
})
})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.
// 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.
// 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.
// 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.
// 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.
// 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
})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.
<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.
<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.
// 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.
# 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 punMulai 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
Bagikan
Artikel terkait

Pertanyaan wawancara Vue.js: 25 pertanyaan untuk meraih pekerjaan
Persiapkan wawancara Vue.js dengan 25 pertanyaan penting ini. Dari reaktivitas hingga composables, kuasai konsep utama untuk wawancara berikutnya.

Nuxt 4 di Tahun 2026: Struktur Direktori Baru dan Panduan Migrasi dari Nuxt 3
Panduan lengkap Nuxt 4 mencakup struktur direktori app/, singleton data fetching, shallow reactivity, serta langkah migrasi bertahap dari Nuxt 3 untuk developer Vue.js.

Vue 3 Pinia vs Vuex: Panduan Lengkap State Management dan Pertanyaan Interview 2026
Perbandingan mendalam Pinia vs Vuex: arsitektur, TypeScript support, Composition API, performa, strategi migrasi, dan pertanyaan interview Vue state management 2026.