@contentrain/skills 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +102 -0
- package/dist/index.cjs +35 -0
- package/dist/index.d.cts +13 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +13 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +35 -0
- package/dist/index.mjs.map +1 -0
- package/frameworks/astro.md +334 -0
- package/frameworks/expo.md +66 -0
- package/frameworks/next.md +296 -0
- package/frameworks/node.md +63 -0
- package/frameworks/nuxt.md +274 -0
- package/frameworks/react-native.md +80 -0
- package/frameworks/react.md +71 -0
- package/frameworks/sveltekit.md +324 -0
- package/frameworks/vue.md +76 -0
- package/package.json +62 -0
- package/workflows/contentrain-bulk.md +92 -0
- package/workflows/contentrain-content.md +154 -0
- package/workflows/contentrain-diff.md +55 -0
- package/workflows/contentrain-doctor.md +55 -0
- package/workflows/contentrain-generate.md +352 -0
- package/workflows/contentrain-init.md +91 -0
- package/workflows/contentrain-model.md +134 -0
- package/workflows/contentrain-normalize.md +294 -0
- package/workflows/contentrain-review.md +162 -0
- package/workflows/contentrain-serve.md +88 -0
- package/workflows/contentrain-translate.md +172 -0
- package/workflows/contentrain-validate-fix.md +85 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
# Contentrain + Next.js 14+ (App Router)
|
|
2
|
+
|
|
3
|
+
> Framework guide for consuming Contentrain-managed content in Next.js applications using the App Router.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Setup
|
|
8
|
+
|
|
9
|
+
### 1.1 SDK Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pnpm add @contentrain/query # or npm/yarn
|
|
13
|
+
npx contentrain generate
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The generator reads `.contentrain/models/` and produces a typed client in `.contentrain/client/`. Your `package.json` must include the subpath import:
|
|
17
|
+
|
|
18
|
+
```json
|
|
19
|
+
{
|
|
20
|
+
"imports": {
|
|
21
|
+
"#contentrain": {
|
|
22
|
+
"types": "./.contentrain/client/index.d.ts",
|
|
23
|
+
"import": "./.contentrain/client/index.mjs",
|
|
24
|
+
"require": "./.contentrain/client/index.cjs",
|
|
25
|
+
"default": "./.contentrain/client/index.mjs"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The generator adds this automatically. Restart the Next.js dev server after initial generation.
|
|
32
|
+
|
|
33
|
+
### 1.2 Watch Mode
|
|
34
|
+
|
|
35
|
+
Run the generator alongside Next.js dev:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npx contentrain generate --watch &
|
|
39
|
+
npx next dev
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## 2. Imports
|
|
45
|
+
|
|
46
|
+
All SDK functions are imported from the `#contentrain` subpath:
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { query, singleton, dictionary, document } from '#contentrain'
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
This import works in React Server Components, server actions, route handlers, and `generateStaticParams`. The SDK reads JSON files from the file system — no runtime API calls.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## 3. Querying Content
|
|
57
|
+
|
|
58
|
+
### 3.1 Collections
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
const posts = query('blog-post').locale('en').all()
|
|
62
|
+
const featured = query('blog-post').locale('en').where('featured', true).all()
|
|
63
|
+
const sorted = query('blog-post').locale('en').sort('publishedAt', 'desc').all()
|
|
64
|
+
const post = query('blog-post').locale('en').where('id', 'abc-123').first()
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 3.2 Singletons
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
const hero = singleton('hero').locale('en').get()
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 3.3 Dictionaries
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
const label = dictionary('ui-labels').locale('en').get('submit_button')
|
|
77
|
+
const allLabels = dictionary('ui-labels').locale('en').get()
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 3.4 Documents
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
const article = document('blog-article').locale('en').bySlug('getting-started')
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### 3.5 Relations
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
const posts = query('blog-post').locale('en').include('author', 'tags').all()
|
|
90
|
+
// post.author → full author object
|
|
91
|
+
// post.tags → array of full tag objects
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## 4. App Router Integration
|
|
97
|
+
|
|
98
|
+
### 4.1 React Server Components
|
|
99
|
+
|
|
100
|
+
SDK calls work directly in Server Components — no `use` wrapper, no `useEffect`, no client-side fetching:
|
|
101
|
+
|
|
102
|
+
```tsx
|
|
103
|
+
// app/blog/page.tsx
|
|
104
|
+
import { query } from '#contentrain'
|
|
105
|
+
|
|
106
|
+
export default async function BlogPage() {
|
|
107
|
+
const posts = query('blog-post').locale('en').sort('publishedAt', 'desc').all()
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<ul>
|
|
111
|
+
{posts.map(post => (
|
|
112
|
+
<li key={post.id}>{post.title}</li>
|
|
113
|
+
))}
|
|
114
|
+
</ul>
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### 4.2 Dynamic Routes with Locale
|
|
120
|
+
|
|
121
|
+
For `app/[locale]/blog/[slug]/page.tsx`:
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
import { document } from '#contentrain'
|
|
125
|
+
|
|
126
|
+
interface Props {
|
|
127
|
+
params: Promise<{ locale: string; slug: string }>
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export default async function PostPage({ params }: Props) {
|
|
131
|
+
const { locale, slug } = await params
|
|
132
|
+
const post = document('blog-article').locale(locale).bySlug(slug)
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<article>
|
|
136
|
+
<h1>{post.title}</h1>
|
|
137
|
+
<div>{post.body}</div>
|
|
138
|
+
</article>
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### 4.3 Static Params Generation
|
|
144
|
+
|
|
145
|
+
Pre-generate all content pages at build time:
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
import { query } from '#contentrain'
|
|
149
|
+
|
|
150
|
+
export async function generateStaticParams() {
|
|
151
|
+
const posts = query('blog-post').locale('en').all()
|
|
152
|
+
return posts.map(post => ({ slug: post.slug }))
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
For multi-locale static generation:
|
|
157
|
+
|
|
158
|
+
```tsx
|
|
159
|
+
import { query } from '#contentrain'
|
|
160
|
+
|
|
161
|
+
const locales = ['en', 'tr', 'de']
|
|
162
|
+
|
|
163
|
+
export async function generateStaticParams() {
|
|
164
|
+
const params = []
|
|
165
|
+
for (const locale of locales) {
|
|
166
|
+
const posts = query('blog-post').locale(locale).all()
|
|
167
|
+
for (const post of posts) {
|
|
168
|
+
params.push({ locale, slug: post.slug })
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return params
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### 4.4 Route Handlers
|
|
176
|
+
|
|
177
|
+
SDK works in route handlers for API-style access:
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
// app/api/posts/route.ts
|
|
181
|
+
import { query } from '#contentrain'
|
|
182
|
+
import { NextResponse } from 'next/server'
|
|
183
|
+
|
|
184
|
+
export function GET() {
|
|
185
|
+
const posts = query('blog-post').locale('en').all()
|
|
186
|
+
return NextResponse.json(posts)
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### 4.5 Client Components
|
|
191
|
+
|
|
192
|
+
Client Components cannot import `#contentrain` directly (it reads from the file system). Fetch data in a Server Component and pass it as props:
|
|
193
|
+
|
|
194
|
+
```tsx
|
|
195
|
+
// Server Component
|
|
196
|
+
import { singleton } from '#contentrain'
|
|
197
|
+
import HeroClient from './HeroClient'
|
|
198
|
+
|
|
199
|
+
export default async function HeroSection() {
|
|
200
|
+
const hero = singleton('hero').locale('en').get()
|
|
201
|
+
return <HeroClient data={hero} />
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## 5. i18n Integration
|
|
208
|
+
|
|
209
|
+
### 5.1 With next-intl
|
|
210
|
+
|
|
211
|
+
```tsx
|
|
212
|
+
import { query } from '#contentrain'
|
|
213
|
+
import { getLocale } from 'next-intl/server'
|
|
214
|
+
|
|
215
|
+
export default async function BlogPage() {
|
|
216
|
+
const locale = await getLocale()
|
|
217
|
+
const posts = query('blog-post').locale(locale).all()
|
|
218
|
+
// ...
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### 5.2 With Built-in i18n Routing
|
|
223
|
+
|
|
224
|
+
Use the `[locale]` route segment to determine the active locale, then pass it to SDK calls. Contentrain dictionaries can supplement or replace `next-intl` message files for content-driven strings.
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## 6. Markdown and MDX
|
|
229
|
+
|
|
230
|
+
### 6.1 Document Body Rendering
|
|
231
|
+
|
|
232
|
+
For document-kind entries with markdown bodies, use `next-mdx-remote` or a markdown renderer:
|
|
233
|
+
|
|
234
|
+
```tsx
|
|
235
|
+
import { document } from '#contentrain'
|
|
236
|
+
import { MDXRemote } from 'next-mdx-remote/rsc'
|
|
237
|
+
|
|
238
|
+
export default async function DocPage({ params }: Props) {
|
|
239
|
+
const { slug } = await params
|
|
240
|
+
const doc = document('doc-page').locale('en').bySlug(slug)
|
|
241
|
+
|
|
242
|
+
return (
|
|
243
|
+
<article>
|
|
244
|
+
<h1>{doc.title}</h1>
|
|
245
|
+
<MDXRemote source={doc.body} />
|
|
246
|
+
</article>
|
|
247
|
+
)
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### 6.2 Custom Components in MDX
|
|
252
|
+
|
|
253
|
+
Pass custom component mappings to the MDX renderer to control how markdown elements render in your design system.
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## 7. Build and Deployment
|
|
258
|
+
|
|
259
|
+
### 7.1 Static Export
|
|
260
|
+
|
|
261
|
+
Contentrain content is fully static. Use `output: 'export'` in `next.config.js` for full static generation:
|
|
262
|
+
|
|
263
|
+
```js
|
|
264
|
+
// next.config.js
|
|
265
|
+
module.exports = {
|
|
266
|
+
output: 'export'
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### 7.2 ISR and Server Mode
|
|
271
|
+
|
|
272
|
+
In server mode, SDK calls execute at request time (or at build time for statically generated pages). Content is still file-based — no external API dependency. Redeploy to pick up content changes.
|
|
273
|
+
|
|
274
|
+
### 7.3 Deployment Flow
|
|
275
|
+
|
|
276
|
+
1. Content changes are committed to Git via MCP tools.
|
|
277
|
+
2. Push triggers platform rebuild (Vercel, Netlify).
|
|
278
|
+
3. `next build` runs, SDK reads `.contentrain/` content.
|
|
279
|
+
4. Static pages are generated with embedded content.
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## 8. Type Safety
|
|
284
|
+
|
|
285
|
+
The generated client provides full TypeScript types:
|
|
286
|
+
|
|
287
|
+
```tsx
|
|
288
|
+
import { query } from '#contentrain'
|
|
289
|
+
|
|
290
|
+
// Fully typed — autocomplete for all fields
|
|
291
|
+
const posts = query('blog-post').locale('en').all()
|
|
292
|
+
posts[0].title // string
|
|
293
|
+
posts[0].author // Author type when using .include('author')
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
Keep the generator running in watch mode during development to stay in sync with model changes.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Contentrain + Node.js
|
|
2
|
+
|
|
3
|
+
> Framework guide for direct server-side use of the generated Contentrain client.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. ESM Usage
|
|
8
|
+
|
|
9
|
+
For ESM apps, import from `#contentrain` directly:
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { query, singleton, dictionary, document } from '#contentrain'
|
|
13
|
+
|
|
14
|
+
const hero = singleton('hero').locale('en').get()
|
|
15
|
+
const posts = query('blog-post').locale('en').all()
|
|
16
|
+
const labels = dictionary('ui-labels').locale('en').get()
|
|
17
|
+
const article = document('blog-article').locale('en').bySlug('getting-started')
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
This is the correct path for:
|
|
21
|
+
|
|
22
|
+
- modern Node ESM apps
|
|
23
|
+
- server scripts
|
|
24
|
+
- Express/Fastify codebases running as ESM
|
|
25
|
+
|
|
26
|
+
## 2. CommonJS Usage
|
|
27
|
+
|
|
28
|
+
For CommonJS consumers, initialize first:
|
|
29
|
+
|
|
30
|
+
```js
|
|
31
|
+
async function run() {
|
|
32
|
+
const client = await require('#contentrain').init()
|
|
33
|
+
const hero = client.singleton('hero').locale('en').get()
|
|
34
|
+
const posts = client.query('blog-post').locale('en').all()
|
|
35
|
+
return { hero, posts }
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Use this pattern for:
|
|
40
|
+
|
|
41
|
+
- NestJS
|
|
42
|
+
- legacy CommonJS servers
|
|
43
|
+
- mixed CJS toolchains
|
|
44
|
+
|
|
45
|
+
## 3. Express Example
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import express from 'express'
|
|
49
|
+
import { query } from '#contentrain'
|
|
50
|
+
|
|
51
|
+
const app = express()
|
|
52
|
+
|
|
53
|
+
app.get('/api/posts', (_req, res) => {
|
|
54
|
+
res.json(query('blog-post').locale('en').all())
|
|
55
|
+
})
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## 4. Rules
|
|
59
|
+
|
|
60
|
+
- ESM: direct `import` from `#contentrain`
|
|
61
|
+
- CJS: `await require('#contentrain').init()`
|
|
62
|
+
- do not expect runtime writes; this is generated static content access
|
|
63
|
+
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
# Contentrain + Nuxt 3
|
|
2
|
+
|
|
3
|
+
> Framework guide for consuming Contentrain-managed content in Nuxt 3 applications.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Setup
|
|
8
|
+
|
|
9
|
+
### 1.1 SDK Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pnpm add @contentrain/query # or npm/yarn
|
|
13
|
+
npx contentrain generate
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The generator reads `.contentrain/models/` and produces a typed client in `.contentrain/client/`. Your `package.json` must include the subpath import:
|
|
17
|
+
|
|
18
|
+
```json
|
|
19
|
+
{
|
|
20
|
+
"imports": {
|
|
21
|
+
"#contentrain": {
|
|
22
|
+
"types": "./.contentrain/client/index.d.ts",
|
|
23
|
+
"import": "./.contentrain/client/index.mjs",
|
|
24
|
+
"require": "./.contentrain/client/index.cjs",
|
|
25
|
+
"default": "./.contentrain/client/index.mjs"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The generator adds this automatically. After generation, restart the Nuxt dev server to pick up the new import map.
|
|
32
|
+
|
|
33
|
+
### 1.2 Watch Mode
|
|
34
|
+
|
|
35
|
+
Run the generator in watch mode alongside Nuxt dev:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npx contentrain generate --watch &
|
|
39
|
+
npx nuxt dev
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Any model or content change under `.contentrain/` triggers client regeneration automatically.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 2. Imports
|
|
47
|
+
|
|
48
|
+
All SDK functions are imported from the `#contentrain` subpath:
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import { query, singleton, dictionary, document } from '#contentrain'
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Treat this import as **server-only** in Nuxt. Use it in Nitro server routes, server utilities, route middleware, and other server execution contexts. Do **not** import `#contentrain` directly in client-rendered Vue components or generic `<script setup>` blocks.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 3. Querying Content
|
|
59
|
+
|
|
60
|
+
### 3.1 Collections
|
|
61
|
+
|
|
62
|
+
Collections return arrays. The SDK converts the internal object-map storage to arrays automatically.
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
const posts = query('blog-post').locale('en').all()
|
|
66
|
+
const featured = query('blog-post').locale('en').where('featured', true).all()
|
|
67
|
+
const sorted = query('blog-post').locale('en').sort('publishedAt', 'desc').all()
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Single entry by ID:
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
const post = query('blog-post').locale('en').where('id', 'abc-123').first()
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 3.2 Singletons
|
|
77
|
+
|
|
78
|
+
Singletons return a single object. Use `.get()` instead of `.all()`.
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
const hero = singleton('hero').locale('en').get()
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### 3.3 Dictionaries
|
|
85
|
+
|
|
86
|
+
Dictionaries provide key-value access for UI strings.
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
const labels = dictionary('ui-labels').locale('en').get('submit_button')
|
|
90
|
+
const allLabels = dictionary('ui-labels').locale('en').get()
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 3.4 Documents
|
|
94
|
+
|
|
95
|
+
Documents are long-form content with metadata and a body field (markdown or MDX).
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
const article = document('blog-article').locale('en').bySlug('getting-started')
|
|
99
|
+
const docs = document('doc-page').locale('en').all()
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 3.5 Relations
|
|
103
|
+
|
|
104
|
+
Resolve relation fields to full objects with `.include()`:
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
const posts = query('blog-post').locale('en').include('author', 'tags').all()
|
|
108
|
+
// post.author → full author object instead of just the ID
|
|
109
|
+
// post.tags → array of full tag objects
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## 4. Nuxt Integration Patterns
|
|
115
|
+
|
|
116
|
+
### 4.1 Page Data Loading
|
|
117
|
+
|
|
118
|
+
Load content through a server route, then consume it with `useAsyncData` in the page:
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
// server/api/blog-posts.get.ts
|
|
122
|
+
import { query } from '#contentrain'
|
|
123
|
+
import { getQuery } from 'h3'
|
|
124
|
+
|
|
125
|
+
export default defineEventHandler((event) => {
|
|
126
|
+
const locale = getQuery(event).locale?.toString() ?? 'en'
|
|
127
|
+
return query('blog-post').locale(locale).sort('publishedAt', 'desc').all()
|
|
128
|
+
})
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
```vue
|
|
132
|
+
<script setup lang="ts">
|
|
133
|
+
const { data: posts } = await useAsyncData('blog-posts', () => $fetch('/api/blog-posts'))
|
|
134
|
+
</script>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### 4.2 Dynamic Routes
|
|
138
|
+
|
|
139
|
+
For `pages/blog/[slug].vue`:
|
|
140
|
+
|
|
141
|
+
```vue
|
|
142
|
+
<script setup lang="ts">
|
|
143
|
+
const route = useRoute()
|
|
144
|
+
const { data: post } = await useAsyncData(
|
|
145
|
+
`post-${route.params.slug}`,
|
|
146
|
+
() => $fetch(`/api/blog-post/${route.params.slug}`),
|
|
147
|
+
)
|
|
148
|
+
</script>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
With the matching server route:
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
// server/api/blog-post/[slug].get.ts
|
|
155
|
+
import { document } from '#contentrain'
|
|
156
|
+
import { getRouterParam } from 'h3'
|
|
157
|
+
|
|
158
|
+
export default defineEventHandler((event) => {
|
|
159
|
+
const slug = getRouterParam(event, 'slug')
|
|
160
|
+
return document('blog-article').locale('en').bySlug(slug ?? '')
|
|
161
|
+
})
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### 4.3 Server Routes
|
|
165
|
+
|
|
166
|
+
Content is also accessible in Nuxt server routes (`server/api/`):
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
// server/api/posts.get.ts
|
|
170
|
+
import { query } from '#contentrain'
|
|
171
|
+
|
|
172
|
+
export default defineEventHandler(() => {
|
|
173
|
+
return query('blog-post').locale('en').all()
|
|
174
|
+
})
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## 5. i18n Integration
|
|
180
|
+
|
|
181
|
+
### 5.1 With @nuxtjs/i18n
|
|
182
|
+
|
|
183
|
+
Use the `useI18n()` composable to get the current locale, then pass it to SDK calls:
|
|
184
|
+
|
|
185
|
+
```vue
|
|
186
|
+
<script setup lang="ts">
|
|
187
|
+
const { locale } = useI18n()
|
|
188
|
+
const { data: posts } = await useAsyncData(
|
|
189
|
+
`posts-${locale.value}`,
|
|
190
|
+
() => $fetch('/api/blog-posts', { query: { locale: locale.value } }),
|
|
191
|
+
{ watch: [locale] }
|
|
192
|
+
)
|
|
193
|
+
</script>
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### 5.2 Dictionary Strings as i18n Source
|
|
197
|
+
|
|
198
|
+
Contentrain dictionaries can serve as the i18n message source. Export dictionary content and feed it to `@nuxtjs/i18n` via a custom loader, or use SDK calls directly in components for content-driven strings.
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## 6. Markdown and Document Content
|
|
203
|
+
|
|
204
|
+
### 6.1 Nuxt Content Module
|
|
205
|
+
|
|
206
|
+
If using `@nuxt/content`, point it to `.contentrain/content/` for markdown files:
|
|
207
|
+
|
|
208
|
+
```ts
|
|
209
|
+
// nuxt.config.ts
|
|
210
|
+
export default defineNuxtConfig({
|
|
211
|
+
content: {
|
|
212
|
+
sources: {
|
|
213
|
+
contentrain: {
|
|
214
|
+
driver: 'fs',
|
|
215
|
+
base: '.contentrain/content'
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
})
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### 6.2 Rendering Document Bodies
|
|
223
|
+
|
|
224
|
+
For document-kind entries, the `body` field contains markdown. Render it with `@nuxt/content` or any markdown renderer:
|
|
225
|
+
|
|
226
|
+
```vue
|
|
227
|
+
<template>
|
|
228
|
+
<ContentRenderer :value="parsedBody" />
|
|
229
|
+
</template>
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## 7. Static Generation and Deployment
|
|
235
|
+
|
|
236
|
+
### 7.1 Static Generation
|
|
237
|
+
|
|
238
|
+
Contentrain content is file-based. `nuxt generate` reads all content at build time:
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
npx nuxt generate
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
All SDK calls resolve to static JSON reads — no runtime API calls during generation.
|
|
245
|
+
|
|
246
|
+
### 7.2 Deployment
|
|
247
|
+
|
|
248
|
+
Content lives in Git. The deployment flow:
|
|
249
|
+
|
|
250
|
+
1. Author or agent creates/updates content via MCP tools.
|
|
251
|
+
2. Changes are committed to a branch and reviewed.
|
|
252
|
+
3. After merge, push triggers platform rebuild (Vercel, Netlify, Cloudflare Pages).
|
|
253
|
+
4. `nuxt generate` runs, SDK reads `.contentrain/` content, static site is produced.
|
|
254
|
+
|
|
255
|
+
### 7.3 Hybrid Rendering
|
|
256
|
+
|
|
257
|
+
For SSR pages, SDK calls execute on the server at request time. Content is still read from the file system — no external API dependency.
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## 8. Type Safety
|
|
262
|
+
|
|
263
|
+
The generated client includes full TypeScript types for every model:
|
|
264
|
+
|
|
265
|
+
```ts
|
|
266
|
+
import { query } from '#contentrain'
|
|
267
|
+
|
|
268
|
+
// Fully typed — IDE autocomplete for field names, return types
|
|
269
|
+
const posts = query('blog-post').locale('en').all()
|
|
270
|
+
// posts[0].title — string
|
|
271
|
+
// posts[0].author — Author (when using .include('author'))
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Type definitions are regenerated whenever models change. Keep the generator running in watch mode during development.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Contentrain + React Native
|
|
2
|
+
|
|
3
|
+
> Framework guide for bare React Native / Metro consumers of the generated Contentrain client.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Setup
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @contentrain/query
|
|
11
|
+
npx contentrain generate
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
React Native uses the generated CommonJS bootstrap path. Do not expect direct ESM `#contentrain` imports inside Metro components.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 2. Bootstrap the Client Once
|
|
19
|
+
|
|
20
|
+
Create a small loader module:
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
// src/contentrain.ts
|
|
24
|
+
let clientPromise: Promise<any> | null = null
|
|
25
|
+
|
|
26
|
+
export function getContentrainClient() {
|
|
27
|
+
if (!clientPromise) {
|
|
28
|
+
clientPromise = require('#contentrain').init()
|
|
29
|
+
}
|
|
30
|
+
return clientPromise
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
This ensures the generated client is initialized once and reused across screens.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 3. Use It in Screens
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
import { useEffect, useState } from 'react'
|
|
42
|
+
import { Text, View } from 'react-native'
|
|
43
|
+
import { getContentrainClient } from './contentrain'
|
|
44
|
+
|
|
45
|
+
export function HomeScreen() {
|
|
46
|
+
const [heroTitle, setHeroTitle] = useState('')
|
|
47
|
+
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
getContentrainClient().then((client) => {
|
|
50
|
+
setHeroTitle(client.singleton('hero').locale('en').get().title)
|
|
51
|
+
})
|
|
52
|
+
}, [])
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<View>
|
|
56
|
+
<Text>{heroTitle}</Text>
|
|
57
|
+
</View>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Collections and dictionaries work the same way:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
getContentrainClient().then((client) => {
|
|
66
|
+
const posts = client.query('blog-post').locale('en').all()
|
|
67
|
+
const labels = client.dictionary('ui-labels').locale('en').get()
|
|
68
|
+
})
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## 4. Rules
|
|
74
|
+
|
|
75
|
+
- initialize with `require('#contentrain').init()`
|
|
76
|
+
- cache the promise; do not initialize in every component render
|
|
77
|
+
- treat the generated client as static read-only data
|
|
78
|
+
- re-run `contentrain generate` after model or content changes
|
|
79
|
+
|
|
80
|
+
If the project is Expo-based, use the dedicated Expo guide. The runtime pattern is similar, but Expo-specific tooling and structure differ.
|