Kompatibilitas 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:
pathcollection Tina (misalcontent/pages) relatif ke root project dan udah termasukcontent/. GlobsourceNuxt Content (misalpages/*.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: truedi-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(denganfieldsnested) →z.object()secara rekursif,rich-text→z.any()(Tina simpan rich-text sebagai AST, bukan string). - Field dengan
list: truedibungkusz.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.