Rilis Nuxt 3 terukur berarti setiap deploy punya sinyal yang jelas: aplikasi bisa start dengan konfigurasi benar, endpoint penting sehat, error dan latency terpantau, serta tim tahu kapan harus rollback. Fokusnya bukan membuat pipeline yang rumit, tetapi memastikan aplikasi Nuxt/Nitro punya mekanisme teknis untuk mendeteksi regresi sebelum dan sesudah traffic produksi masuk.

Artikel ini membahas alur praktis untuk developer Nuxt 3/Nitro: validasi environment variable, build artifact, health check, smoke test, strategi blue-green atau canary sederhana, rollback cepat, structured log, metrik error/latency, dan template postmortem ringan setelah insiden.

1. Alur sebelum deploy: pastikan rilis bisa diverifikasi

Sebelum membahas endpoint dan log, sepakati dulu prinsip dasar rilis. Setiap versi aplikasi sebaiknya dapat diidentifikasi, dijalankan ulang, dan dibandingkan dengan versi sebelumnya. Ini penting untuk rollback karena Anda perlu tahu persis artifact mana yang sedang berjalan dan artifact mana yang aman untuk dikembalikan.

Checklist sebelum build

  • Kunci dependency: gunakan lockfile dari package manager yang sama di local, CI, dan server build.
  • Jalankan typecheck dan test relevan: minimal lint/typecheck, unit test untuk utilitas kritis, dan test server route penting.
  • Validasi environment variable: jangan biarkan aplikasi berhasil start dengan konfigurasi kosong atau salah format.
  • Catat identitas rilis: commit SHA, nomor build, nama branch/tag, dan waktu build.
  • Pastikan migrasi database kompatibel: hindari perubahan destruktif yang membuat rollback aplikasi tidak mungkin.

Untuk Nuxt 3, hasil build produksi umumnya berada di direktori .output setelah menjalankan build. Artifact inilah yang sebaiknya dipromosikan antar environment, bukan membangun ulang dari source yang mungkin sudah berubah.

npm ci
npm run build
node .output/server/index.mjs

Jika Anda memakai container, prinsipnya sama: image yang sudah berhasil diuji dipromosikan ke staging lalu produksi. Jangan membangun ulang image berbeda untuk produksi dari commit yang sama kecuali memang ada alasan kuat.

2. Validasi environment variable di runtime Nitro

Banyak insiden Nuxt bukan berasal dari kode SSR yang kompleks, tetapi dari konfigurasi yang tidak lengkap: base URL API kosong, secret tidak tersedia, atau mode fitur aktif di environment yang salah. Karena sebagian konfigurasi Nuxt/Nitro dibaca saat runtime, validasi sebaiknya dilakukan ketika server start.

Nuxt menyediakan runtimeConfig untuk konfigurasi runtime. Konfigurasi privat hanya tersedia di server, sedangkan public dapat diekspos ke client. Jangan menaruh secret di runtimeConfig.public.

// nuxt.config.ts
export default defineNuxtConfig({
  runtimeConfig: {
    apiSecret: '',
    apiBaseUrl: '',
    public: {
      appVersion: process.env.APP_VERSION || 'local'
    }
  }
})

Contoh berikut menggunakan Nitro plugin untuk memvalidasi konfigurasi saat server start. Jika konfigurasi wajib tidak ada, proses dibuat gagal lebih awal daripada menghasilkan error acak saat request pertama masuk.

// server/plugins/validate-env.ts
export default defineNitroPlugin(() => {
  const required = ['NUXT_API_SECRET', 'NUXT_API_BASE_URL']
  const missing = required.filter((key) => !process.env[key])

  if (missing.length > 0) {
    console.error(JSON.stringify({
      level: 'fatal',
      event: 'env_validation_failed',
      missing
    }))

    throw new Error(`Missing required environment variables: ${missing.join(', ')}`)
  }

  try {
    new URL(process.env.NUXT_API_BASE_URL as string)
  } catch {
    throw new Error('NUXT_API_BASE_URL must be a valid URL')
  }
})

Catatan: untuk validasi yang lebih lengkap, Anda dapat memakai schema validator seperti Zod. Namun jangan sampai validasi hanya ada di dokumentasi; validasi harus dijalankan oleh aplikasi.

Kesalahan umum

  • Mengandalkan default kosong: string kosong membuat aplikasi start, tetapi gagal saat memanggil API.
  • Menaruh secret di public runtime config: nilai public dapat terbaca dari sisi client.
  • Berbeda nama env antara build dan runtime: dokumentasikan nama env yang benar dan cek di startup.
  • Tidak memvalidasi format: URL, angka timeout, dan boolean feature flag perlu diparsing, bukan hanya dicek ada.

3. Endpoint health check untuk liveness dan readiness

Health check membantu load balancer, orchestrator, dan smoke test menentukan apakah aplikasi layak menerima traffic. Pisahkan dua jenis sinyal: liveness untuk mengetahui proses masih hidup, dan readiness untuk mengetahui aplikasi siap melayani request nyata.

Liveness: proses Nitro masih merespons

Liveness sebaiknya murah dan tidak bergantung pada database atau layanan eksternal. Jika endpoint ini gagal, kemungkinan proses bermasalah atau server tidak bisa menerima request.

// server/routes/health.get.ts
export default defineEventHandler(() => {
  return {
    status: 'ok',
    service: 'nuxt-app',
    version: process.env.APP_VERSION || 'unknown',
    time: new Date().toISOString()
  }
})

Readiness: dependency penting bisa diakses

Readiness boleh mengecek dependency penting, tetapi harus diberi timeout pendek agar health check tidak menggantung. Jangan menjalankan query berat. Cukup panggilan ringan ke database, cache, atau API internal yang benar-benar diperlukan agar aplikasi berfungsi.

// server/routes/ready.get.ts
function withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T> {
  return Promise.race([
    promise,
    new Promise<T>((_, reject) => {
      setTimeout(() => reject(new Error('timeout')), timeoutMs)
    })
  ])
}

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig()
  const checks: Record<string, string> = {}

  try {
    await withTimeout($fetch(`${config.apiBaseUrl}/health`), 1500)
    checks.api = 'ok'
  } catch (error) {
    checks.api = 'failed'
    setResponseStatus(event, 503)

    return {
      status: 'not_ready',
      checks
    }
  }

  return {
    status: 'ready',
    checks
  }
})

Jangan memasukkan detail sensitif ke respons health check. Cukup status dependency, bukan URL privat, token, stack trace, atau payload error lengkap.

4. Smoke test setelah deploy

Smoke test adalah validasi cepat bahwa artifact yang baru dirilis benar-benar bisa melayani request dasar di environment target. Ini berbeda dari test lengkap. Tujuannya mendeteksi masalah konfigurasi, routing, asset, SSR, atau dependency utama sebelum traffic diperbesar.

Contoh smoke test sederhana:

BASE_URL=https://staging.example.com

curl -fsS $BASE_URL/health
curl -fsS $BASE_URL/ready
curl -fsS $BASE_URL/ > /tmp/home.html
curl -fsS $BASE_URL/api/profile-schema

Untuk aplikasi Nuxt, smoke test yang berguna biasanya mencakup:

  • Endpoint health dan ready: memastikan proses dan dependency utama sehat.
  • Halaman SSR utama: misalnya homepage, halaman login, atau halaman katalog.
  • Server route/API Nitro penting: terutama route yang memakai runtime config atau auth.
  • Asset publik: memastikan build asset dapat diakses dari origin/CDN.
  • Alur minimal dengan auth: bila memungkinkan, gunakan akun test terbatas untuk memeriksa sesi/login.

Jika smoke test gagal, jangan lanjutkan promosi traffic. Investigasi log rilis tersebut, perbaiki konfigurasi atau kode, lalu build artifact baru. Hindari memperbaiki manual di server tanpa jejak karena akan menyulitkan rollback dan audit.

5. Blue-green dan canary sederhana untuk Nuxt 3

Nuxt 3 dengan Nitro umumnya cocok dijalankan sebagai proses stateless. Artinya strategi blue-green atau canary dapat dilakukan di layer platform: load balancer, reverse proxy, container orchestrator, atau fitur deployment provider. Yang penting, state seperti session, cache, file upload, dan job queue tidak hanya disimpan di memori proses baru.

Blue-green

Pada blue-green deployment, Anda menjalankan dua environment atau slot:

  • Blue: versi produksi yang sedang menerima traffic.
  • Green: versi baru yang sudah deploy tetapi belum menerima traffic utama.

Alurnya sederhana: deploy ke green, jalankan health check dan smoke test, lalu alihkan traffic dari blue ke green. Jika terjadi regresi, traffic dikembalikan ke blue.

Pilih blue-green jika Anda membutuhkan rollback sangat cepat dan punya kapasitas menjalankan dua versi aplikasi secara bersamaan.

Canary

Pada canary deployment, versi baru menerima sebagian kecil traffic terlebih dahulu. Jika metrik tetap sehat, traffic dinaikkan bertahap. Jika error naik, hentikan canary dan kembalikan semua traffic ke versi lama.

Pilih canary jika Anda ingin mendeteksi masalah yang hanya muncul pada traffic nyata, misalnya variasi browser, data user tertentu, atau beban SSR yang berbeda dari staging.

Hal yang perlu dijaga

  • Session harus kompatibel: jika cookie/session berubah format, versi lama dan baru harus bisa membaca selama transisi.
  • Migrasi database harus backward compatible: gunakan pola expand-contract. Tambah kolom dulu, deploy kode kompatibel, baru hapus field lama pada rilis berikutnya.
  • Cache jangan membuat versi bercampur secara berbahaya: cache key perlu mempertimbangkan perubahan format respons jika perlu.
  • Feature flag lebih aman daripada deploy besar: untuk fitur berisiko, deploy kode dalam keadaan nonaktif lalu aktifkan bertahap.

6. Rollback cepat: apa yang harus disiapkan

Rollback bukan rencana darurat yang ditulis setelah insiden. Rollback harus menjadi bagian dari desain rilis. Untuk Nuxt/Nitro, rollback cepat berarti Anda bisa menjalankan kembali artifact atau image sebelumnya, mengarahkan traffic ke versi lama, dan memastikan konfigurasi runtime kompatibel.

Langkah rollback praktis

  1. Bekukan promosi traffic: hentikan canary atau jangan alihkan blue-green lebih lanjut.
  2. Kembalikan artifact sebelumnya: redeploy .output lama atau image container sebelumnya.
  3. Alihkan traffic: arahkan load balancer/proxy ke versi lama yang sudah sehat.
  4. Validasi: jalankan /health, /ready, dan smoke test inti.
  5. Pantau metrik: pastikan error rate dan latency kembali normal.
  6. Catat timeline: simpan waktu mulai insiden, waktu rollback, dan sinyal pemulihan.

Sinyal kapan rollback

Rollback sebaiknya diputuskan berdasarkan sinyal yang disepakati, bukan perasaan. Contoh sinyal yang wajar:

  • Health/readiness gagal pada instance baru.
  • Smoke test gagal untuk route utama.
  • Rasio respons 5xx naik signifikan dibanding baseline normal.
  • Latency p95/p99 naik dan berdampak ke halaman atau API utama.
  • Error client meningkat setelah asset baru dirilis.
  • Login, checkout, pembayaran, atau alur bisnis kritis gagal.
  • Log menunjukkan error konfigurasi seperti env kosong, secret salah, atau endpoint dependency tidak valid.

Jika masalah hanya terjadi pada fitur yang dilindungi feature flag, mematikan flag bisa lebih cepat dan lebih rendah risiko daripada rollback penuh. Namun jika regresi berada di SSR, routing, auth, konfigurasi, atau dependency global, rollback biasanya lebih aman.

7. Observability Nitro: structured log dan metrik yang bisa ditindaklanjuti

Log Nitro yang baik harus bisa dijadikan dasar debugging dan metrik. Hindari log berupa kalimat bebas saja. Gunakan structured log JSON ke stdout/stderr agar mudah dikumpulkan oleh platform logging.

Contoh plugin berikut menambahkan request id, durasi, status code, path, method, dan error. Field ini cukup untuk membuat dashboard error rate dan latency per route.

// server/plugins/request-log.ts
import { randomUUID } from 'node:crypto'
import {
  getRequestHeader,
  getRequestIP,
  getRequestURL,
  setResponseHeader
} from 'h3'

export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('request', (event) => {
    const requestId = getRequestHeader(event, 'x-request-id') || randomUUID()
    event.context.requestId = requestId
    event.context.startedAt = Date.now()
    setResponseHeader(event, 'x-request-id', requestId)
  })

  nitroApp.hooks.hook('afterResponse', (event) => {
    const url = getRequestURL(event)
    const durationMs = Date.now() - Number(event.context.startedAt || Date.now())

    console.log(JSON.stringify({
      level: 'info',
      event: 'request_completed',
      request_id: event.context.requestId,
      method: event.node.req.method,
      path: url.pathname,
      status: event.node.res.statusCode,
      duration_ms: durationMs,
      ip: getRequestIP(event, { xForwardedFor: true })
    }))
  })

  nitroApp.hooks.hook('error', (error, context) => {
    const event = context.event

    console.error(JSON.stringify({
      level: 'error',
      event: 'request_failed',
      request_id: event?.context.requestId,
      message: error.message,
      stack: process.env.NODE_ENV === 'production' ? undefined : error.stack
    }))
  })
})

Di produksi, berhati-hatilah dengan PII dan secret. Jangan mencatat token, cookie, password, payload pembayaran, atau data personal yang tidak diperlukan. Jika perlu debugging payload, gunakan redaction dan sampling.

Metrik minimal yang perlu dipantau

  • 5xx rate: indikator error server dari SSR, API Nitro, atau dependency.
  • 4xx rate yang tidak biasa: bisa menunjukkan routing/auth berubah.
  • Latency p95/p99: lebih representatif daripada rata-rata untuk pengalaman user.
  • Jumlah restart proses: menandakan crash loop atau masalah memori.
  • Readiness failure: dependency tidak siap atau konfigurasi salah.
  • Error per route: membantu menentukan apakah rollback penuh atau feature flag cukup.

Jika platform Anda mendukung OpenTelemetry atau APM, structured log tetap berguna sebagai pelengkap. Trace membantu mengikuti request lintas layanan, sedangkan log memberi konteks detail saat terjadi error.

8. Checklist rilis Nuxt 3 yang bisa langsung dipakai

Sebelum deploy

  • Lockfile dipakai konsisten.
  • Typecheck, lint, dan test relevan lulus.
  • Env wajib tervalidasi dan tidak ada secret di config public.
  • Artifact .output atau image diberi identitas rilis.
  • Migrasi database kompatibel dengan versi lama dan baru.
  • Endpoint /health dan /ready tersedia.
  • Structured log aktif dengan request id dan durasi.

Saat deploy

  • Deploy ke slot green atau subset canary.
  • Jalankan smoke test terhadap URL target.
  • Periksa log startup dan error awal.
  • Pastikan readiness berhasil sebelum menerima traffic.
  • Naikkan traffic bertahap jika memakai canary.

Setelah deploy

  • Pantau 5xx rate, latency p95/p99, dan error per route.
  • Bandingkan dengan baseline sebelum rilis.
  • Pastikan tidak ada restart berulang.
  • Simpan catatan rilis: versi, waktu deploy, operator, dan perubahan utama.
  • Siapkan keputusan rollback jika sinyal melewati ambang yang disepakati.

9. Setelah insiden: template postmortem ringan

Postmortem tidak harus panjang. Yang penting adalah menghasilkan tindakan pencegahan agar regresi yang sama tidak terulang. Hindari fokus pada menyalahkan individu; fokus pada celah sistem rilis, observability, test, dan desain aplikasi.

Judul insiden:
Tanggal dan durasi:
Versi rilis / commit:
Dampak user:
- Siapa yang terdampak?
- Fitur atau route apa yang gagal?

Timeline:
- Waktu deploy:
- Sinyal pertama terdeteksi:
- Keputusan rollback / mitigasi:
- Waktu pulih:

Akar masalah:
- Bug kode, konfigurasi, migrasi, dependency, atau proses rilis?

Mengapa tidak terdeteksi lebih awal:
- Test apa yang belum ada?
- Health check atau smoke test apa yang kurang?
- Log/metrik apa yang tidak cukup jelas?

Tindakan pencegahan:
- Tambah validasi env:
- Tambah smoke test:
- Tambah alert metrik:
- Ubah strategi migrasi:
- Tambah feature flag:

Owner dan tenggat:
- Item 1:
- Item 2:

Contoh tindakan pencegahan yang konkret

  • Menambahkan validasi format URL untuk dependency yang gagal.
  • Menambahkan smoke test untuk server route yang sebelumnya tidak diuji.
  • Memisahkan liveness dan readiness agar instance tidak menerima traffic sebelum dependency siap.
  • Membuat migrasi database backward compatible untuk dua versi aplikasi.
  • Menambahkan log field route, request_id, dan duration_ms agar debugging lebih cepat.
  • Melindungi fitur berisiko dengan feature flag agar mitigasi tidak selalu perlu rollback penuh.

Penutup

Rilis Nuxt 3 yang aman tidak bergantung pada satu alat. Kombinasinya adalah artifact yang jelas, validasi environment, health check yang tepat, smoke test singkat, strategi traffic bertahap, rollback yang sudah dilatih, dan log Nitro yang bisa dianalisis. Dengan sinyal yang terukur, keputusan deploy atau rollback menjadi lebih objektif dan waktu pemulihan saat insiden bisa dipersingkat.