Guide

Kompatibilitas Nuxt Content

nuxt-tina jalan standalone: ini soal bridge opsional dan manual ke @nuxt/content.

Kamu nggak butuh Nuxt Content sama sekali. useTina, TinaMarkdown, TinaProvider, dan bridge visual editing semuanya baca langsung dari GraphQL client Tina sendiri, nggak ada dependency ke @nuxt/content sama sekali. Nggak diinstall sama module ini, dan nggak ada yang rusak tanpa itu.

Semua yang di bawah ini buat orang yang juga mau query konten yang sama lewat Nuxt Content (search, listing, queryCollection()) bersamaan sama editing Tina. @nuxt/content bukan dependency module ini; install sendiri:

npm install @nuxt/content

Dua sistem ini nggak ngobrol otomatis

Field-nya tetap ditulis sekali di tina/config.ts, terus kamu arahkan dua config-nya ke file yang sama secara manual:

  • Path folder: path collection Tina (misal content/pages) relatif ke root project dan udah termasuk content/. Glob source Nuxt Content (misal pages/*.md) relatif ke content directory-nya sendiri (content/ secara default) dan nggak ngulang prefix itu. Salah di sini, dua sistemnya diam-diam nunjuk ke file yang beda.
  • Schema: daripada nulis bentuk frontmatter dua kali (sekali sebagai field Tina, sekali lagi sebagai schema Zod), turunkan schema Nuxt Content dari field Tina yang udah kamu tulis.

tinaFieldsToZodShape()

Diekspor dari nuxt-tina/utils. Ini helper satu arah, jalan di build-time: field Tina → shape Zod: bukan sync runtime. Nggak ada helper buat arah sebaliknya, jadi tina/config.ts tetap satu-satunya tempat definisi field-nya berada.

// content.config.ts
import { defineCollection, defineContentConfig, z } from '@nuxt/content'
import { tinaFieldsToZodShape } from 'nuxt-tina/utils'
import tinaConfig from './tina/config'

const postFields = tinaConfig.schema.collections.find(c => c.name === 'post')!.fields

export default defineContentConfig({
  collections: {
    posts: defineCollection({
      type: 'page',
      source: 'posts/*.md',
      schema: z.object(tinaFieldsToZodShape(postFields, z)),
    }),
  },
})

content.config.ts sebaiknya nggak punya definisi field manual sama sekali: cuma panggilan derive ini per collection.

Cara kerja mapping-nya

  • Field yang ditandai isBody: true di-skip: Nuxt Content sendiri yang parse body markdown-nya, itu bukan field frontmatter.
  • Mapping tipe: string/datetime/image → z.string(), number → z.number(), boolean → z.boolean(), object (dengan fields nested) → z.object() secara rekursif, rich-text → z.any() (Tina simpan rich-text sebagai AST, bukan string).
  • Field dengan list: true dibungkus z.array().
  • Semua field jadi .optional() di sisi Zod: validasi "wajib diisi" tetap ditangani sama form editor Tina; Nuxt Content di sini cuma butuh tau bentuk datanya buat query.

Kalau satu field butuh validasi Zod yang lebih strict daripada tipe hasil derive-nya (misal format email), override aja setelah spread-nya, nggak usah nulis ulang seluruh schema-nya manual:

schema: z.object({
  ...tinaFieldsToZodShape(postFields, z),
  email: z.string().email(),
})

Kapan ini nggak cocok dipakai

Mapping-nya cuma satu arah dan cuma nutupin kasus umum. Kalau collection kamu butuh validasi Zod yang lebih spesifik (regex, enum, custom refinement) secara keseluruhan, tulis schema-nya manual seperti biasa: helper ini opsional, nggak wajib.

Copyright © 2026