Media
Default: berbasis git
Tanpa konfigurasi tambahan, file yang diupload lewat field image langsung di-commit ke repo, di bawah media.tina di tina/config.ts:
media: {
tina: {
mediaRoot: 'uploads',
publicFolder: 'public',
},
}
Opsi paling simpel, nggak butuh infrastruktur tambahan, tapi setiap upload jadi commit Git.
Backend media handler
Buat external storage, nuxt-tina nyambungin backend media handler satu-satu. Cloudinary yang udah beneran diimplementasikan sekarang. Yang lain masih di roadmap, belum tersedia:
| Backend | Status |
|---|---|
| Cloudinary | Tersedia |
| AWS S3 Bucket | Segera hadir |
| DigitalOcean Spaces | Segera hadir |
media.provider sekarang cuma nerima 'tina' \| 'cloudinary': coba isi yang lain langsung jadi type error, bukan opsi yang diam-diam diabaikan.
Cloudinary
nuxt-tina udah nyediain adapter Cloudinary siap pakai lewat next-tinacms-cloudinary (bukan hard dependency: cuma di-import secara dinamis pas kamu beneran pakai).
1. Aktifkan server route-nya
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['nuxt-tina'],
tina: {
media: {
provider: 'cloudinary', // default: 'tina'
},
},
})
Ini ngedaftarin /api/cloudinary/** (adapter Nitro buat createMediaHandler dari next-tinacms-cloudinary), cuma kalau diaktifkan, jadi kalau kamu nggak pakai Cloudinary, package itu nggak pernah ke-bundle sama sekali.
2. Setup credentials-nya
Cloudinary butuh tiga set env var yang berbeda. Ini bukan duplikasi yang bisa dipangkas. Masing-masing dibaca sama consumer yang beda:
# Dibaca sama Nitro runtime (handler /api/cloudinary/**)
NUXT_TINA_CLOUDINARY_CLOUD_NAME=<cloud-name>
NUXT_TINA_CLOUDINARY_API_KEY=<api-key>
NUXT_TINA_CLOUDINARY_API_SECRET=<api-secret>
# Dibaca sama CLI tinacms (tinacms build / tinacms dev)
TINA_TOKEN=<sama-dengan-NUXT_TINA_TOKEN>
TINA_PUBLIC_CLIENT_ID=<sama-dengan-NUXT_TINA_CLIENT_ID>
# Dibaca sama next-tinacms-cloudinary (package ini dari ekosistem Next.js)
NEXT_PUBLIC_TINA_CLIENT_ID=<sama-dengan-NUXT_TINA_CLIENT_ID>
| Var | Dibaca sama | Kenapa format ini |
|---|---|---|
NUXT_TINA_* | Nitro server runtime | Konvensi prefix env Nuxt sendiri |
TINA_TOKEN + TINA_PUBLIC_CLIENT_ID | CLI tinacms | CLI-nya punya konvensi naming sendiri |
NEXT_PUBLIC_TINA_CLIENT_ID | internal next-tinacms-cloudinary | Package ini hardcode baca env var dengan prefix NEXT_PUBLIC_ |
3. Install dependency-nya
pnpm add next-tinacms-cloudinary @tinacms/auth
4. Arahkan tina/config.ts ke Cloudinary
import { tinaCloudinaryMediaStore } from 'nuxt-tina/media'
export default defineConfig({
media: {
loadCustomStore: tinaCloudinaryMediaStore,
},
})
⚠️ Gotcha: jangan pakai process.env di sini buat toggle on/off
Bagian media di tina/config.ts juga ke-bundle ke JavaScript yang jalan di browser admin (nggak cuma dieksekusi di server): @tinacms/cli nyalin source mentah file ini buat bagian yang mesti jalan di client-side (loadCustomStore, ui.component custom, dll), bukan hasil evaluasi Node-nya.
Artinya kalau kamu nulis:
// ❌ SALAH: process.env selalu undefined di browser
const useCloudinary = !!process.env.NUXT_TINA_CLOUDINARY_CLOUD_NAME
media: useCloudinary ? { loadCustomStore: tinaCloudinaryMediaStore } : { tina: {...} }
...toggle-nya selalu balik ke default di admin (Media Manager browsing filesystem project, bukan Cloudinary), meskipun credentials-nya valid dan server route-nya jalan normal pas ditest langsung pakai curl. Cek tina/__generated__/config.prebuild.jsx kalau mau lihat buktinya: ekspresi process.env.X-nya ada di situ apa adanya, nggak pernah di-inline jadi true/false.
Fix-nya: kalau kamu mau nyalain/matiin Cloudinary, ubah langsung nilai literal-nya di source:
// ✅ BENAR: literal yang fixed, bukan ekspresi env
const useCloudinary = true // ubah manual, jangan baca env var
Toggle di sisi server (media.provider di nuxt.config.ts) nggak kena masalah ini: itu murni Node/build-time dan jalan normal pakai env var biasa.