Nuxt nedir, ne zaman seçilir?
Nuxt, Vue üzerine kurulu, açık kaynaklı bir web uygulaması çatısıdır. Vue size bileşen ve reaktiflik modelini verir; Nuxt bunun etrafına dosya tabanlı yönlendirme, sunucu tarafında render (SSR), veri çekme kalıpları, otomatik içe aktarmalar ve Nitro adlı sunucu motorunu ekler. Böylece aynı projede hem kullanıcının gördüğü sayfaları hem de bu sayfaların konuştuğu küçük API uçlarını yazabilirsiniz. Bu yazıyı hazırladığım 6 Ekim 2026 itibarıyla güncel kararlı sürüm 5 Ağustos 2026'da yayımlanan Nuxt 4.5.2; altında Nitro 2.13.4 çalışıyor. Nuxt 3'ün desteği 31 Temmuz 2026'da sona erdi, bu yüzden yeni bir projeye Nuxt 4 ile başlamak gerekiyor.
Nuxt'ı seçmek için en güçlü gerekçe, ilk yanıtın anlamlı HTML olarak gelmesinin önemli olduğu projelerdir: içerik siteleri, kataloglar, blog ve dokümantasyon sayfaları, arama motorunda bulunması gereken ürün ekranları. Sunucu ile istemci arasında aynı tipleri ve doğrulama kurallarını paylaşmak, tek bir kod tabanında hem arayüzü hem API'yi yönetmek de Nuxt'ı cazip kılar. Buna karşılık yalnızca giriş yapmış kullanıcıların gördüğü küçük bir yönetim paneli, başka bir sayfaya gömülen bir widget ya da SEO beklentisi olmayan tek ekranlık bir araç için Nuxt'ın getirdiği çalışma zamanı ve yapılandırma yükü gereksiz olabilir. Doğru soru “en iyi çatı hangisi” değil, “bu projenin ilk yanıtı nerede, ne zaman üretilmeli” sorusudur.
Bu rehber boyunca tek bir örnek projeyi büyüteceğiz: Anadolu Ürün Rehberi. Yerel üreticilerin el yapımı ürünlerini listeleyen, arama yapılabilen, her ürünün ayrı bir detay sayfası olan ve Türkçe ile İngilizce yayın yapan küçük bir katalog. Başlangıçta ürünleri bellekte tutacağız; bunun bilinçli bir sadeleştirme olduğunu, gerçek stok ve fiyat bilgisinin bir veritabanında ya da güvenilir bir servis arkasında durması gerektiğini baştan söyleyeyim. Nuxt varsayılan olarak SSR açık gelir; aşağıdaki ayar bu davranışı yalnızca görünür kılar.
01export default defineNuxtConfig({02 ssr: true,03})Kurulum ve Nuxt 4 proje yapısı
Nuxt 4'ün kurulum belgesi Node.js 22 veya daha yeni bir sürüm istiyor ve 22 ya da 24 gibi çift numaralı, tercihen aktif LTS sürümünü öneriyor. Önce node -v ile sürümünüzü kontrol edin; ardından resmî başlatıcı ile projeyi oluşturup geliştirme sunucusunu açın. -o bayrağı tarayıcıyı otomatik açar.
01npm create nuxt@latest anadolu-urun-rehberi02cd anadolu-urun-rehberi03npm run dev -- -oNuxt 4'ün en görünür değişikliği klasör düzenidir. Vue uygulamasına ait her şey artık varsayılan olarak app/ klasöründe durur: sayfalar, bileşenler, layout'lar, composable'lar, middleware ve eklentiler. Sunucu kodu ise kök dizindeki server/ klasöründe kalır. Kökteki shared/ klasörü hem Vue uygulamasının hem de Nitro sunucusunun kullanabileceği kodlar içindir; örneğin iki tarafın da kullandığı ürün tipi veya bir fiyat biçimlendirme fonksiyonu. public/ statik dosyaları olduğu gibi sunar. Nuxt 3 düzenindeki projeler otomatik algılandığı için taşıma zorunlu değildir; ama yeni bir projede bu ayrımı korumak, hangi kodun tarayıcıya gidebileceğini ve hangisinin yalnızca sunucuda kalması gerektiğini ilk bakışta gösterir.
Bu ayrımı küçük bir güvenlik sınırı gibi düşünün. server/ altındaki bir dosya veritabanı parolası okuyabilir; app/ altındaki bir bileşen ise tarayıcıya gönderilecek pakete girer. Gizli bir anahtarı yanlışlıkla bir bileşene taşımak, onu herkesin indirebileceği JavaScript dosyasına koymak anlamına gelir. shared/ içine yalnızca iki tarafta da güvenle çalışabilecek, gizli bilgi taşımayan kodları koyun.
Sayfalar, dosya tabanlı yönlendirme ve layout
Nuxt'ta yönlendirme tablosu yazmazsınız; app/pages/ altındaki dosya adları adres olur. pages/index.vue ana sayfayı, pages/products/index.vue ürün listesini, pages/products/[slug].vue ise köşeli parantezdeki parametreyle her ürünün detay sayfasını oluşturur. Sayfalar arası geçişte NuxtLink kullanın: hem doğru <a> etiketi üretir hem de bağlantı görünür olduğunda hedef sayfanın kodunu önceden yükleyebilir. Uygulamanın kökü olan app.vue dosyası, sayfayı ve onu saran layout'u nereye çizeceğini söyler.
01<template>02 <NuxtLayout>03 <NuxtPage />04 </NuxtLayout>05</template>Layout'lar, birden çok sayfanın paylaştığı çerçevedir: üst menü, alt bilgi, ortak boşluklar. app/layouts/default.vue dosyası varsayılan çerçevedir ve sayfanın içeriği <slot /> yerine yerleşir. Bir sayfanın farklı bir layout kullanması gerekiyorsa bunu definePageMeta ile sayfanın kendi içinde belirtirsiniz; böylece yönetim paneli gibi bölümler kendi sade çerçevesini kullanabilir.
01<template>02 <div>03 <header><NuxtLink to="/">Anadolu Ürün Rehberi</NuxtLink></header>04 <main><slot /></main>05 </div>06</template>01<script setup lang="ts">02const route = useRoute()03 04definePageMeta({05 layout: 'default',06})07</script>08 09<template>10 <article>11 <h1>Ürün detayı</h1>12 <p>Slug: {{ route.params.slug }}</p>13 </article>14</template>Burada küçük ama önemli bir ayrıntı var: route.params.slug kullanıcıdan gelen bir değerdir. Sayfada göstermek sorun değildir, çünkü Vue şablonları metni otomatik kaçışlar. Ama bu değeri bir API adresine eklerken encodeURIComponent ile kodlayacağız ve sunucuda mutlaka doğrulayacağız. Yönlendirme kolaylığı, girdinin güvenilir olduğu anlamına gelmez.
Veri çekme: useFetch, useAsyncData ve $fetch
Nuxt'ın en çok yanlış anlaşılan, ama en değerli parçası veri çekme katmanıdır. Bir sayfa sunucuda render edilirken useFetch veya useAsyncData ile çektiğiniz veri, HTML ile birlikte “payload” adı verilen bir paket içinde tarayıcıya gönderilir. Tarayıcı sayfayı hidrasyonla canlandırırken aynı anahtara sahip çağrı bu paketi okur ve ilk isteği bir daha yapmaz. Bileşenin setup kısmında doğrudan $fetch kullanırsanız bu devir teslim olmaz: aynı istek bir kez sunucuda, bir kez tarayıcıda çalışabilir. Kural basit: sayfa açılırken gereken veri için composable'ları, kullanıcı bir düğmeye bastığında yapılan işlemler için $fetch'i kullanın.
01<script setup lang="ts">02interface Product {03 id: number04 slug: string05 name: string06 price: number07}08 09const { data: products, status, error, refresh } = await useFetch<Product[]>('/api/products', {10 key: 'catalog:products',11 pick: ['id', 'slug', 'name', 'price'],12 default: () => [],13})14 15async function sendInquiry(productSlug: string) {16 await $fetch('/api/inquiries', {17 method: 'POST',18 body: { productSlug },19 })20}21</script>22 23<template>24 <section>25 <p v-if="status === 'pending'">Ürünler yükleniyor…</p>26 <p v-else-if="error" role="alert">Ürünler alınamadı.</p>27 <ul v-else>28 <li v-for="product in products" :key="product.id">29 <NuxtLink :to="`/products/${product.slug}`">{{ product.name }}</NuxtLink>30 <span>{{ product.price }} ₺</span>31 <button @click="sendInquiry(product.slug)">Bilgi iste</button>32 </li>33 </ul>34 <button @click="refresh()">Yenile</button>35 </section>36</template>Bu örnekte birkaç bilinçli tercih var. Açık bir key verdik, çünkü Nuxt 4'te aynı anahtarı kullanan çağrılar aynı data, error ve status değerlerini paylaşır; bu yüzden aynı anahtarlı çağrılarda pick, transform ve default gibi seçenekleri de tutarlı tutmak gerekir. pick yalnızca listede gereken alanları payload'a koyar ve HTML'e gömülen veriyi küçültür; ancak sunucudan alınan yanıtın kendisini küçültmez. Nuxt 4'te data ve error başlangıçta undefined gelir ve veri varsayılan olarak yüzeysel reaktiftir, bu yüzden default ile boş bir dizi vermek şablonu sadeleştirir. Şablonda status ile yükleniyor, error ile hata durumunu ayrı ayrı gösteriyoruz.
Bir CMS istemcisi veya kendi yazdığınız bir async fonksiyon kullanıyorsanız useAsyncData daha uygundur. Detay sayfasında anahtarın ürüne özel olması kritiktir: sabit bir anahtar, önceden render edilen sayfalarda bir ürünün verisinin başka bir ürünün sayfasına sızmasına yol açabilir. Anahtarı bir fonksiyonla, adres parametresine bağlı üretin.
01const route = useRoute()02const slug = computed(() => String(route.params.slug))03const { data: product, error } = await useAsyncData(04 () => `catalog:product:${slug.value}`,05 () => $fetch(`/api/products/${encodeURIComponent(slug.value)}`),06)Kritik olmayan, sayfanın görünmesini beklemesi gerekmeyen veriler için ise isteği tarayıcıya erteleyebilirsiniz. server: false seçeneği isteğin hidrasyon tamamlandıktan sonra başlaması demektir; bu süre boyunca status değerine göre bir yükleniyor durumu göstermek kullanıcıya dürüst bir geri bildirim verir.
01const { data: reviews, status } = useLazyFetch('/api/reviews', {02 server: false,03 default: () => [],04})Nitro ile API ve çalışma zamanı doğrulaması
Nuxt'ın sunucu tarafını Nitro yürütür. server/api/ altına koyduğunuz her dosya bir API ucu olur ve dosya adındaki .get, .post gibi son ekler hangi HTTP yöntemine yanıt verdiğini belirler. Örneğimizde ürün verisini server/utils/products.ts içinde tutuyor, listeyi ve detayı iki ayrı uçtan sunuyoruz. #server takma adı Nuxt 4.3 ile geldi ve yalnızca sunucu kodunda kullanılabilir; bu da sunucuya ait bir modülün yanlışlıkla tarayıcı paketine girmesini zorlaştırır.
01export const products = [02 { id: 1, slug: 'seramik-kupa', name: 'Seramik kupa', price: 420, description: 'El yapımı seramik kupa.' },03 { id: 2, slug: 'dokuma-canta', name: 'Dokuma çanta', price: 890, description: 'Yerel dokumadan çanta.' },04]TypeScript tipleri derleme sırasında silinir; internetten gelen bir sorgu parametresinin tipini kanıtlamaz. Bu yüzden arama ucunda gelen q parametresini zod ile çalışma zamanında doğruluyor, geçersiz girdide anlaşılır bir 400 yanıtı dönüyoruz. Aramada toLocaleLowerCase('tr') kullanmak Türkçe için önemlidir: büyük “I” harfinin “ı”, büyük “İ” harfinin “i” olarak küçülmesini sağlar ve “Iğdır” araması doğru sonuç verir.
01import { z } from 'zod'02import { products } from '#server/utils/products'03 04const querySchema = z.object({05 q: z.string().trim().max(80).optional(),06})07 08export default defineEventHandler((event) => {09 const parsed = querySchema.safeParse(getQuery(event))10 if (!parsed.success) {11 throw createError({ statusCode: 400, statusMessage: 'Geçersiz arama parametresi' })12 }13 14 const q = parsed.data.q?.toLocaleLowerCase('tr')15 return q16 ? products.filter(product => product.name.toLocaleLowerCase('tr').includes(q))17 : products18})01import { products } from '#server/utils/products'02 03export default defineEventHandler((event) => {04 const slug = getRouterParam(event, 'slug')05 const product = products.find(item => item.slug === slug)06 if (!product) {07 throw createError({ statusCode: 404, statusMessage: 'Ürün bulunamadı' })08 }09 return product10})Kullanıcının “Bilgi iste” düğmesine bastığında çalışan uç ise gövdeyi doğruluyor. Bu örnek talebi hiçbir yere kaydetmiyor; gerçek bir projede yanıt vermeden önce talebi veritabanına yazmanız ya da bir kuyruğa göndermeniz gerekir. Gizli anahtarları ise koda değil, runtimeConfig üzerinden ortam değişkenlerine koyun ve hiçbir zaman runtimeConfig.public altına yerleştirmeyin; o bölüm tarayıcıya gönderilir.
01import { z } from 'zod'02 03const inquirySchema = z.object({04 productSlug: z.string().min(1).max(120),05})06 07export default defineEventHandler(async (event) => {08 const parsed = inquirySchema.safeParse(await readBody(event))09 if (!parsed.success) {10 throw createError({ statusCode: 400, statusMessage: 'Geçersiz talep' })11 }12 13 return { received: true, productSlug: parsed.data.productSlug }14})Render modları ve routeRules ile hibrit yapı
Nuxt'ın asıl gücü, her sayfanın nasıl üretileceğine ayrı ayrı karar verebilmenizdir. SSR'da HTML her istekte sunucuda üretilir: veri tazedir ama her istek sunucuda iş demektir. SSG ya da prerender'da sayfa derleme sırasında üretilir: barındırmak çok kolaydır ama içerik değiştiğinde yeniden derleme gerekir. SPA modunda (ssr: false) tarayıcıya boş bir uygulama kabuğu gider ve arayüz tarayıcıda çizilir; arama motoru açısından en zayıf seçenektir ama özel paneller için yeterlidir. Hibrit yapı ise bu modları adres bazında birleştirir.
Kataloğumuz için mantıklı bir karışım şu: ana sayfa nadiren değiştiği için derleme sırasında üretilsin, ürün sayfaları bir saatlik önbellekle “stale-while-revalidate” (SWR) mantığıyla sunulsun, yönetim paneli ise yalnızca tarayıcıda çalışsın. SWR'da ziyaretçi önbellekteki sayfayı hemen alır, arka planda yeni sürüm hazırlanır. Bunun bedeli, fiyat değiştikten sonra kısa bir süre eski fiyatın görünebilmesidir; ürününüz için bu kabul edilebilir mi, buna siz karar vermelisiniz. Şunu da unutmayın: ssr: false yalnızca render biçimini değiştirir, yönetim paneli adresini veya API'sini korumaz; kimlik doğrulama ve yetkilendirme her zaman sunucuda yapılmalıdır.
01export default defineNuxtConfig({02 routeRules: {03 '/': { prerender: true },04 '/products/**': { swr: 3600 },05 '/admin/**': { ssr: false },06 },07})İki uyarıyı aklınızda tutun. Birincisi, kişiye özel yanıtları (sepet, hesap bilgisi) asla paylaşılan önbelleğe koymayın; SWR herkese aynı sayfayı verir. İkincisi, bu hibrit kurallar nuxt generate ile oluşturulan tamamen statik çıktıda çalışmaz; çalışan bir Nitro sunucusu ister. isr kuralı da her platformda aynı davranmaz: Nuxt belgeleri CDN önbelleğiyle ISR desteği için Vercel ve Netlify'ı gösteriyor. Tamamen statik bir site istiyorsanız dinamik ürün adreslerinin derleme sırasında keşfedildiğinden ya da listelendiğinden emin olun.
Render modu bir teknoloji tercihi değil, ürün kararıdır: her adres için “ne kadar taze olmalı, ne kadar ucuza sunulmalı?” sorusunu cevaplayın.
SEO ve Türkçe/İngilizce içerik
SSR, arama motorlarına anlamlı HTML verir ama başlık, açıklama ve dil etiketlerini sizin doğru kurmanız gerekir. Her ürün sayfası kendi başlığını ve açıklamasını yüklenen veriden almalı. useSeoMeta reaktif değerleri kabul eder ve değerleri kaçışlayarak etiketlere yazar; ürün bulunamazsa bile anlamlı bir varsayılan gösterin.
01const route = useRoute()02const { data: product } = await useFetch<{ name: string; description: string }>(03 () => `/api/products/${encodeURIComponent(String(route.params.slug))}`,04)05const title = computed(() => product.value?.name ?? 'Ürün bulunamadı')06const description = computed(() => product.value?.description ?? 'Anadolu Ürün Rehberi')07useSeoMeta({ title, description, ogTitle: title, ogDescription: description })İki dilli yayın için resmî i18n modülünü ve site haritası modülünü ekleyin. prefix_except_default stratejisi Türkçe adresleri önek olmadan, İngilizce adresleri /en/ önekiyle üretir. language alanına tr-TR ve en-US gibi tam dil etiketleri yazmak, baseUrl ile de üretim alan adını vermek önemlidir; aksi hâlde hreflang ve canonical bağlantıları mutlak adres olarak üretilemez.
01npx nuxi@latest module add i18n02npx nuxi@latest module add sitemap01export default defineNuxtConfig({02 modules: ['@nuxtjs/i18n', '@nuxtjs/sitemap'],03 i18n: {04 baseUrl: 'https://katalog.example',05 defaultLocale: 'tr',06 strategy: 'prefix_except_default',07 locales: [08 { code: 'tr', language: 'tr-TR', file: 'tr.json' },09 { code: 'en', language: 'en-US', file: 'en.json' },10 ],11 },12})01{ "catalog": { "title": "Ürünler", "empty": "Ürün bulunamadı" } }01{ "catalog": { "title": "Products", "empty": "No products found" } }01<script setup lang="ts">02const localeHead = useLocaleHead({ seo: true })03 04useHead(() => ({05 htmlAttrs: localeHead.value.htmlAttrs ?? {},06 link: localeHead.value.link ?? [],07 meta: localeHead.value.meta ?? [],08}))09</script>10 11<template><NuxtLayout><NuxtPage /></NuxtLayout></template>Burada önemli bir sınır var: i18n modülü adresleri, arayüz metinlerini ve SEO etiketlerini iki dile taşır, ama ürün verisini kendiliğinden çevirmez. Şimdiye kadar ürünlerimiz yalnızca Türkçeydi. Gerçekten iki dilli bir katalog için her ürünün adını ve açıklamasını iki dilde tutun ve API'nin istenen dili döndürmesini sağlayın. Dil parametresini de diğer girdiler gibi doğrulayın.
01export const products = [02 {03 id: 1,04 slug: 'seramik-kupa',05 price: 420,06 name: { tr: 'Seramik kupa', en: 'Ceramic mug' },07 description: { tr: 'El yapımı seramik kupa.', en: 'Handmade ceramic mug.' },08 },09 {10 id: 2,11 slug: 'dokuma-canta',12 price: 890,13 name: { tr: 'Dokuma çanta', en: 'Woven bag' },14 description: { tr: 'Yerel dokumadan çanta.', en: 'A bag made from local weaving.' },15 },16]Liste ucu artık hem aramayı hem dili doğruluyor; arama, istenen dildeki ürün adında yapılıyor. Detay ucu da aynı dil parametresini doğrulayıp ürünün o dildeki adını ve açıklamasını döndürüyor. Bu iki dosya, bölüm 5'teki tek dilli sürümlerin yerini alır.
01import { z } from 'zod'02import { products } from '#server/utils/products'03 04const querySchema = z.object({05 q: z.string().trim().max(80).optional(),06 locale: z.enum(['tr', 'en']).default('tr'),07})08 09export default defineEventHandler((event) => {10 const parsed = querySchema.safeParse(getQuery(event))11 if (!parsed.success) {12 throw createError({ statusCode: 400, statusMessage: 'Geçersiz arama parametresi' })13 }14 15 const { q, locale } = parsed.data16 const needle = q?.toLocaleLowerCase(locale)17 return products18 .map(product => ({19 id: product.id,20 slug: product.slug,21 price: product.price,22 name: product.name[locale],23 description: product.description[locale],24 }))25 .filter(product => !needle || product.name.toLocaleLowerCase(locale).includes(needle))26})01import { z } from 'zod'02import { products } from '#server/utils/products'03 04const querySchema = z.object({05 locale: z.enum(['tr', 'en']).default('tr'),06})07 08export default defineEventHandler((event) => {09 const parsed = querySchema.safeParse(getQuery(event))10 if (!parsed.success) {11 throw createError({ statusCode: 400, statusMessage: 'Geçersiz dil' })12 }13 14 const { locale } = parsed.data15 const product = products.find(item => item.slug === getRouterParam(event, 'slug'))16 if (!product) {17 throw createError({ statusCode: 404, statusMessage: 'Ürün bulunamadı' })18 }19 return { ...product, name: product.name[locale], description: product.description[locale] }20})Sayfalarda aktif dili sorguya ekleyin. query içindeki reaktif değer değiştiğinde useFetch isteği yeniler; böylece kullanıcı dili değiştirdiğinde liste ve detay da o dilde gelir. Detay sayfasındaki SEO etiketleri de artık seçili dildeki ürün verisinden üretilir.
01const { locale } = useI18n()02const { data: products } = await useFetch('/api/products', {03 query: { locale },04 default: () => [],05})01const route = useRoute()02const { locale, t } = useI18n()03const { data: product } = await useFetch<{ name: string; description: string }>(04 () => `/api/products/${encodeURIComponent(String(route.params.slug))}`,05 { query: { locale } },06)07const title = computed(() => product.value?.name ?? t('catalog.empty'))08const description = computed(() => product.value?.description ?? t('catalog.title'))09useSeoMeta({ title, description, ogTitle: title, ogDescription: description })Site haritası modülü statik sayfaları kendisi bulur, ama /products/[slug] gibi dinamik adreslerin hangi ürünlerden oluştuğunu bilemez. Ürün adreslerini bir kaynak ucuyla ona verin. _i18nTransform: true, her adresin Türkçe ve İngilizce karşılığının site haritasına alternatifleriyle girmesini sağlar.
01import { products } from '#server/utils/products'02 03export default defineSitemapEventHandler(() => {04 return products.map(product => ({05 loc: `/products/${product.slug}`,06 _i18nTransform: true,07 }))08})01export default defineNuxtConfig({02 sitemap: {03 sources: ['/api/__sitemap__/urls'],04 },05})Kurulumdan sonra işin bittiğini varsaymayın, çıktıyı kontrol edin. Üretim derlemesindeki bir ürün sayfasının kaynağını açıp iki dilin birbirini hreflang ile gösterdiğini, canonical adresin doğru olduğunu ve iki dildeki ürün adreslerinin site haritasına girdiğini görün. Bu kontrolü kolaylaştırmak için sitemdeki ücretsiz hreflang oluşturucu ve sitemap doğrulayıcı araçlarını kullanabilirsiniz.
Performans ve test
Katalog sitelerinde en ağır yük genellikle görsellerdir. @nuxt/image modülünün <NuxtImg> bileşeni genişlik ve yüksekliği bildirerek sayfa kaymasını önler, sizes ile ekrana uygun boyutu seçer ve ekran dışındaki görselleri loading="lazy" ile ertelemenizi kolaylaştırır. Bileşeni kullanmadan önce modülü kurmanız gerektiğini unutmayın.
01<template>02 <article>03 <NuxtImg04 :src="`/images/${product.slug}.webp`"05 :alt="product.name"06 width="720"07 height="540"08 sizes="100vw sm:50vw lg:33vw"09 loading="lazy"10 />11 <h2>{{ product.name }}</h2>12 </article>13</template>14 15<script setup lang="ts">16defineProps<{ product: { slug: string; name: string } }>()17</script>
JavaScript tarafında da benzer bir disiplin gerekir. Sayfanın ilk görünümünde gerekmeyen bileşenleri Lazy önekiyle ve bir koşula bağlayarak yükleyin; örneğin yorum paneli ancak kullanıcı istediğinde indirilsin. Payload'a koyduğunuz veriyi pick ile sınırlayın, çünkü bu veri HTML'in içine gömülür ve sayfanın ilk yükünü büyütür.
01<script setup lang="ts">02const showReviews = ref(false)03</script>04 05<template>06 <button @click="showReviews = true">Yorumları göster</button>07 <LazyReviewPanel v-if="showReviews" />08</template>Test tarafında resmî @nuxt/test-utils paketi, uygulamanızı gerçek bir sunucu olarak başlatıp sayfa HTML'ini sınamanızı sağlar. Başlangıç şablonu test araçlarını kurmaz; önce paketleri geliştirme bağımlılığı olarak ekleyin. Tarayıcıda çalışan testler için ayrıca Nuxt test belgesindeki ek kurulumu uygulayın.
01npm i -D @nuxt/test-utils vitest02npx vitest runBu kurulumla çalışan aşağıdaki test, ana sayfanın sunucuda gerçekten render edildiğini doğrular. Bunu tarayıcıda gezinme, dil bağlantıları, yükleniyor ve hata durumları ile klavye erişimini sınayan uçtan uca testlerle tamamlayın; tip denetimi tek başına test değildir.
01import { describe, expect, it } from 'vitest'02import { fileURLToPath } from 'node:url'03import { $fetch, setup } from '@nuxt/test-utils/e2e'04 05describe('product catalog', async () => {06 await setup({07 rootDir: fileURLToPath(new URL('../..', import.meta.url)),08 server: true,09 })10 11 it('renders product content on the server', async () => {12 const html = await $fetch('/')13 expect(html).toContain('Anadolu Ürün Rehberi')14 })15})Yayına alma ve sık yapılan hatalar
Nuxt'ı yayına almanın iki temel yolu var. Hibrit kurallar ve API uçları kullanıyorsanız nuxt build ile bir Node sunucusu üretir ve .output/server/index.mjs dosyasını çalıştırırsınız. Hiçbir sunucu kodu çalıştırmayan, tamamen statik bir site için nuxt generate yeterlidir; ama o durumda server/api uçlarınız çalışmaz. Vercel, Netlify veya Cloudflare gibi platformlar için Nitro'nun hazır ayar setleri (preset) vardır; platforma özel derleme ve çalışma zamanı gereksinimlerini seçmeden önce Nitro'nun yayın belgesinden kontrol edin.
01# Node sunucusu / Node server02npx nuxt build03NODE_ENV=production node .output/server/index.mjs04 05# Tamamen statik çıktı / Fully static output06npx nuxt generate01export default defineNuxtConfig({02 nitro: {03 preset: 'node-server',04 },05})Yayından önce aşağıdaki listeyi tek tek kontrol edin. Bu maddelerin çoğu, Nuxt projelerinde canlıya çıktıktan sonra fark edilen ve düzeltmesi daha pahalı olan hatalardır:
- Sayfa açılırken gereken veri için
setupiçinde doğrudan$fetchkullanmayın;useFetchveyauseAsyncDatakullanın. - Dinamik sayfalarda veri anahtarını adres parametresine bağlayın; sabit anahtar veri karışmasına yol açar.
- TypeScript tiplerinin gelen veriyi doğruladığını varsaymayın; sunucuda çalışma zamanı doğrulaması yapın.
- Gizli anahtarları
runtimeConfig.publicaltına koymayın; o bölüm tarayıcıya gider. - Kişiye özel yanıtları SWR veya paylaşılan önbelleğe almayın.
nuxt generateile çalışan bir Nitro sunucusu beklemeyin; dinamik adreslerin prerender edildiğini doğrulayın.- Payload boyutunu ve görselleri üretim derlemesinde ölçün; 404 sayfasını ve geçersiz API girdisini test edin.
- Hedef platformun preset'iyle bir üretim derlemesini yayından önce mutlaka çalıştırın.
Nuxt; doğru kullanıldığında içerik sitelerinden ürün kataloglarına kadar pek çok projede hem hızlı bir ilk yanıt hem de bakımı kolay tek bir kod tabanı sunar. Asıl beceri, çatının sunduğu her özelliği kullanmak değil, her sayfanın nerede ve ne zaman üretileceğini bilerek seçmektir. Bu rehberdeki kataloğu kendi projenize uyarlarken her adımı üretim derlemesinde doğrulayın; geliştirme sunucusunda çalışan her şey canlıda aynı davranmayabilir.
Resmî kaynaklar ve ileri okuma
Bu yazıdaki sürüm bilgileri ve API davranışları aşağıdaki birincil kaynaklardan 6 Ekim 2026'da kontrol edildi:
- Nuxt 4: Introduction
- Nuxt 4: Installation
- Nuxt 4: Directory Structure
- Nuxt 4: Upgrade Guide
- Nuxt Blog: Nuxt 4.5
- Nuxt: GitHub releases
- Nuxt 4: Data Fetching
- Nuxt 4: useFetch
- Nuxt 4: useAsyncData
- Nuxt 4: server/ directory
- Nuxt 4: Rendering Modes
- Nuxt 4: Prerendering
- Nuxt 4: SEO and Meta
- Nuxt i18n: SEO
- Nuxt Image: NuxtImg
- Nuxt 4: Testing
- Nuxt 4: Deployment
- Nitro: Deploy
- Nuxt Design Kit
Nuxt hızlı gelişen bir çatı. Sürüme bağlı ayrıntılarda kendi projenizin kullandığı Nuxt sürümünün belgelerini esas alın. Nuxt logosu Vercel'in tescilli markasıdır; bu yazıda yalnızca Nuxt'ı tanıtmak için kullanılmıştır.
İyi bir Nuxt uygulaması, her sayfanın nerede ve ne zaman üretildiğini bilerek kurulur.
Diğer yazılara göz at ↗