Initial commit
This commit is contained in:
12
.claude-plugin/plugin.json
Normal file
12
.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "tinacms",
|
||||
"description": "Build content-heavy sites with Git-backed TinaCMS. Provides visual editing and content management for blogs, documentation, and marketing sites with non-technical editors. Use when implementing Next.js, Vite+React, or Astro CMS setups, self-hosting on Cloudflare Workers, or troubleshooting ESbuild compilation errors, module resolution issues, or Docker binding problems.",
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "Jeremy Dawes",
|
||||
"email": "jeremy@jezweb.net"
|
||||
},
|
||||
"skills": [
|
||||
"./"
|
||||
]
|
||||
}
|
||||
3
README.md
Normal file
3
README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# tinacms
|
||||
|
||||
Build content-heavy sites with Git-backed TinaCMS. Provides visual editing and content management for blogs, documentation, and marketing sites with non-technical editors. Use when implementing Next.js, Vite+React, or Astro CMS setups, self-hosting on Cloudflare Workers, or troubleshooting ESbuild compilation errors, module resolution issues, or Docker binding problems.
|
||||
154
assets/links-to-official-docs.md
Normal file
154
assets/links-to-official-docs.md
Normal file
@@ -0,0 +1,154 @@
|
||||
# TinaCMS Official Documentation Links
|
||||
|
||||
**Last Updated**: 2025-10-24
|
||||
|
||||
---
|
||||
|
||||
## Core Documentation
|
||||
|
||||
- **Official Website**: https://tina.io
|
||||
- **Documentation Home**: https://tina.io/docs
|
||||
- **Getting Started**: https://tina.io/docs/setup-overview
|
||||
- **What's New**: https://tina.io/whats-new/tinacms
|
||||
|
||||
---
|
||||
|
||||
## Setup & Configuration
|
||||
|
||||
- **Setup Overview**: https://tina.io/docs/setup-overview
|
||||
- **Next.js Setup**: https://tina.io/docs/frameworks/next/overview
|
||||
- **Next.js App Router**: https://tina.io/docs/frameworks/next/app-router
|
||||
- **Next.js Pages Router**: https://tina.io/docs/frameworks/next/pages-router
|
||||
- **Astro Setup**: https://tina.io/docs/frameworks/astro
|
||||
- **Framework-Agnostic**: https://tina.io/docs/frameworks/other
|
||||
|
||||
---
|
||||
|
||||
## Schema & Content Modeling
|
||||
|
||||
- **Schema Overview**: https://tina.io/docs/schema
|
||||
- **Content Modeling Intro**: https://tina.io/docs/content-model-intro
|
||||
- **Collections Reference**: https://tina.io/docs/reference/collections
|
||||
- **Field Types**: https://tina.io/docs/reference/types
|
||||
- **Templates**: https://tina.io/docs/reference/templates
|
||||
|
||||
---
|
||||
|
||||
## Visual Editing
|
||||
|
||||
- **Contextual Editing**: https://tina.io/docs/contextual-editing/overview
|
||||
- **React Visual Editing**: https://tina.io/docs/contextual-editing/react
|
||||
- **Vue Visual Editing**: https://tina.io/docs/contextual-editing/vue
|
||||
|
||||
---
|
||||
|
||||
## Self-Hosting
|
||||
|
||||
- **Self-Hosted Overview**: https://tina.io/docs/self-hosted/overview
|
||||
- **Existing Site**: https://tina.io/docs/self-hosted/existing-site
|
||||
- **Querying Data**: https://tina.io/docs/self-hosted/querying-data
|
||||
- **Next.js Backend**: https://tina.io/docs/reference/self-hosted/tina-backend/nextjs
|
||||
- **Vercel Functions**: https://tina.io/docs/reference/self-hosted/tina-backend/vercel-functions
|
||||
- **Netlify Functions**: https://tina.io/docs/reference/self-hosted/tina-backend/netlify-functions
|
||||
|
||||
---
|
||||
|
||||
## Authentication
|
||||
|
||||
- **Auth Overview**: https://tina.io/docs/reference/self-hosted/auth-provider/overview
|
||||
- **Auth.js Provider**: https://tina.io/docs/reference/self-hosted/auth-provider/authjs
|
||||
- **TinaCloud Provider**: https://tina.io/docs/reference/self-hosted/auth-provider/tinacloud
|
||||
- **Custom Auth**: https://tina.io/docs/reference/self-hosted/auth-provider/bring-your-own
|
||||
|
||||
---
|
||||
|
||||
## Deployment
|
||||
|
||||
- **TinaCloud**: https://tina.io/docs/tinacloud/overview
|
||||
- **Migrating to Self-Hosted**: https://tina.io/docs/self-hosted/migrating-from-tinacloud
|
||||
- **Next.js + Vercel**: https://tina.io/docs/self-hosted/starters/nextjs-vercel
|
||||
|
||||
---
|
||||
|
||||
## Errors & Troubleshooting
|
||||
|
||||
- **FAQ**: https://tina.io/docs/introduction/faq
|
||||
- **Error FAQ**: https://tina.io/docs/errors/faq
|
||||
- **ESbuild Error**: https://tina.io/docs/errors/esbuild-error
|
||||
- **Common Migration Errors**: https://tina.io/docs/forestry/common-errors
|
||||
- **Contributing Troubleshooting**: https://tina.io/docs/contributing/troubleshooting
|
||||
|
||||
---
|
||||
|
||||
## Extending Tina
|
||||
|
||||
- **Extending Overview**: https://tina.io/docs/extending-tina/overview
|
||||
- **Custom Field Components**: https://tina.io/docs/extending-tina/custom-field-components
|
||||
- **Format & Parse**: https://tina.io/docs/extending-tina/format-and-parse
|
||||
- **Custom List Rendering**: https://tina.io/docs/extending-tina/customize-list-ui
|
||||
|
||||
---
|
||||
|
||||
## Media Management
|
||||
|
||||
- **Media Overview**: https://tina.io/docs/reference/media/overview
|
||||
- **Cloudinary**: https://tina.io/docs/reference/media/external/cloudinary
|
||||
- **S3**: https://tina.io/docs/reference/media/external/s3
|
||||
- **Custom Media Store**: https://tina.io/docs/reference/media/external/authentication
|
||||
|
||||
---
|
||||
|
||||
## GraphQL & Querying
|
||||
|
||||
- **GraphQL Overview**: https://tina.io/docs/graphql/overview
|
||||
- **CLI**: https://tina.io/docs/graphql/cli
|
||||
- **Queries**: https://tina.io/docs/graphql/queries
|
||||
|
||||
---
|
||||
|
||||
## Migration Guides
|
||||
|
||||
- **Forestry.io Migration**: https://tina.io/docs/forestry/migrate
|
||||
- **Schema Migration**: https://tina.io/docs/tinacloud/schema-migration
|
||||
- **Content Modeling (Forestry)**: https://tina.io/docs/forestry/content-modelling
|
||||
|
||||
---
|
||||
|
||||
## Blog & Tutorials
|
||||
|
||||
- **Blog Home**: https://tina.io/blog
|
||||
- **Using TinaCMS with Next.js**: https://tina.io/blog/using-tinacms-with-nextjs
|
||||
- **React 19 Support**: https://tina.io/blog/react-19-support
|
||||
- **TinaCMS + Next.js Perfect Match**: https://tina.io/blog/tina-cloud-and-nextjs-the-perfect-match
|
||||
|
||||
---
|
||||
|
||||
## Community & Support
|
||||
|
||||
- **Discord**: https://discord.gg/zumN63Ybpf
|
||||
- **GitHub**: https://github.com/tinacms/tinacms
|
||||
- **GitHub Issues**: https://github.com/tinacms/tinacms/issues
|
||||
- **GitHub Discussions**: https://github.com/tinacms/tinacms/discussions
|
||||
- **Twitter/X**: https://twitter.com/tina_cms
|
||||
|
||||
---
|
||||
|
||||
## Starter Templates
|
||||
|
||||
- **Tina Astro Starter**: https://github.com/tinacms/tina-astro-starter
|
||||
- **Tina Next.js Starter (Alpaca)**: https://github.com/tinacms/tina-starter-alpaca
|
||||
- **Tina Next.js Example**: https://github.com/tinacms/tinacms-next-example
|
||||
|
||||
---
|
||||
|
||||
## API Reference
|
||||
|
||||
- **Config**: https://tina.io/docs/reference/schema
|
||||
- **Collections**: https://tina.io/docs/reference/collections
|
||||
- **Field Types**: https://tina.io/docs/reference/types
|
||||
- **Templates**: https://tina.io/docs/reference/templates
|
||||
- **UI Config**: https://tina.io/docs/reference/collections#ui
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: 2025-10-24
|
||||
129
plugin.lock.json
Normal file
129
plugin.lock.json
Normal file
@@ -0,0 +1,129 @@
|
||||
{
|
||||
"$schema": "internal://schemas/plugin.lock.v1.json",
|
||||
"pluginId": "gh:jezweb/claude-skills:skills/tinacms",
|
||||
"normalized": {
|
||||
"repo": null,
|
||||
"ref": "refs/tags/v20251128.0",
|
||||
"commit": "400c8d7d5b20494cc309834adbcc646e89795c2b",
|
||||
"treeHash": "0bc45f580a6e72fd7a2205584c2c2007615ac0ac5a1b62b6fd9b0d14f5ccc026",
|
||||
"generatedAt": "2025-11-28T10:19:03.520835Z",
|
||||
"toolVersion": "publish_plugins.py@0.2.0"
|
||||
},
|
||||
"origin": {
|
||||
"remote": "git@github.com:zhongweili/42plugin-data.git",
|
||||
"branch": "master",
|
||||
"commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390",
|
||||
"repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data"
|
||||
},
|
||||
"manifest": {
|
||||
"name": "tinacms",
|
||||
"description": "Build content-heavy sites with Git-backed TinaCMS. Provides visual editing and content management for blogs, documentation, and marketing sites with non-technical editors. Use when implementing Next.js, Vite+React, or Astro CMS setups, self-hosting on Cloudflare Workers, or troubleshooting ESbuild compilation errors, module resolution issues, or Docker binding problems.",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"content": {
|
||||
"files": [
|
||||
{
|
||||
"path": "README.md",
|
||||
"sha256": "345374a33d68f21054d4a91608abb47a8ce8b989d51d716cc65962971954b672"
|
||||
},
|
||||
{
|
||||
"path": "SKILL.md",
|
||||
"sha256": "3a23414177453d5fcaf9c0ae3c58ec4deae9fdf48e55e3ac864160538128ae39"
|
||||
},
|
||||
{
|
||||
"path": "references/common-errors.md",
|
||||
"sha256": "965f9864c31c43909351ddd64a590b1a82ac8f9fdb4923c92591c754346e887f"
|
||||
},
|
||||
{
|
||||
"path": "scripts/check-versions.sh",
|
||||
"sha256": "81ffc7c0d58cc5f9fe50d53c57761f0cab3c748861ce6bbc0c1944abbcaeff11"
|
||||
},
|
||||
{
|
||||
"path": ".claude-plugin/plugin.json",
|
||||
"sha256": "5453e8d3dbdbe6b1fb09e5e37fef2736c916a488ccee717748da6ad3e952e1ec"
|
||||
},
|
||||
{
|
||||
"path": "templates/cloudflare-worker-backend/wrangler.jsonc",
|
||||
"sha256": "ec1dc37cc41687d4987f7ed3280493685f72d5bd90355a329d25fd226713a189"
|
||||
},
|
||||
{
|
||||
"path": "templates/cloudflare-worker-backend/src/index.ts",
|
||||
"sha256": "6e8b4b616283809a0924514c0fd7fbd777befb5401d5fa0aa82f4335e36ace77"
|
||||
},
|
||||
{
|
||||
"path": "templates/nextjs/tina-config-pages-router.ts",
|
||||
"sha256": "6ed3b89206df2c0da1876abecf6efbb9c38ee02abcda7db24cc3ab7fb20a114d"
|
||||
},
|
||||
{
|
||||
"path": "templates/nextjs/package.json",
|
||||
"sha256": "5f9441b2927cd8e01bc33f1fbd3c9abf00752536065e98c8fb22c461680d77b3"
|
||||
},
|
||||
{
|
||||
"path": "templates/nextjs/tina-config-app-router.ts",
|
||||
"sha256": "41ff3aa4f21658b29e1619c8202d5bea3d31945aa4f7c32a647d581f07e661f3"
|
||||
},
|
||||
{
|
||||
"path": "templates/nextjs/.env.example",
|
||||
"sha256": "46c87a8855083f88835d3c383a115bd393e8af07260bd92ce5121a4081058d26"
|
||||
},
|
||||
{
|
||||
"path": "templates/astro/astro.config.mjs",
|
||||
"sha256": "4773ee6afe0104912e525eadbbaa225d213c75f315579dd99eb60352ee1fb75a"
|
||||
},
|
||||
{
|
||||
"path": "templates/astro/package.json",
|
||||
"sha256": "cd8be334e9c9fd98e1d17f40e58a9bcfe46ea006efee36f93fb7a50eae332d4b"
|
||||
},
|
||||
{
|
||||
"path": "templates/astro/tina-config.ts",
|
||||
"sha256": "66e71d8bc778b0599a7c3acf852f4b8df73ec3018fdc9333cf45677a9f3ee0c6"
|
||||
},
|
||||
{
|
||||
"path": "templates/astro/.env.example",
|
||||
"sha256": "48b9bd8607b943e920f3acd710c7c8817cdf75da457e1cc73373e9c1d87af9c4"
|
||||
},
|
||||
{
|
||||
"path": "templates/vite-react/package.json",
|
||||
"sha256": "9c06e396c0cca07f6519d3bcaf1da94ce4f198b2cffba204e3fc440a4a303468"
|
||||
},
|
||||
{
|
||||
"path": "templates/vite-react/tina-config.ts",
|
||||
"sha256": "6c33063bda0ee8fa4102621714cc0627dcac98672cdb4b57ae4e113f19440590"
|
||||
},
|
||||
{
|
||||
"path": "templates/vite-react/.env.example",
|
||||
"sha256": "27bc67321a4601b6e6911c8f11f23faf76b406d631ba49d9631f086add46dcc8"
|
||||
},
|
||||
{
|
||||
"path": "templates/vite-react/vite.config.ts",
|
||||
"sha256": "79f38283a8c3a9456ea995fcb8c8ce4bf51a36fe505fbb05ffe3a77f2df59deb"
|
||||
},
|
||||
{
|
||||
"path": "templates/collections/blog-post.ts",
|
||||
"sha256": "533ce87c5594915d6274bcdbc24c2ec80db97ad10e2a46221e35e1fbcf35e237"
|
||||
},
|
||||
{
|
||||
"path": "templates/collections/landing-page.ts",
|
||||
"sha256": "81bc8481e4f657a92948e74b648cbfce23bddf8a6a92cbc01a2ccfdac06d77c9"
|
||||
},
|
||||
{
|
||||
"path": "templates/collections/author.ts",
|
||||
"sha256": "d541878e50c0b5798d830b30143ecf9f5d862c840af254f23e06ce881da77d18"
|
||||
},
|
||||
{
|
||||
"path": "templates/collections/doc-page.ts",
|
||||
"sha256": "c9448083d9eedc42d39ab829e69ba2c7a4de765f86d02cf6d3006ff588d67bc7"
|
||||
},
|
||||
{
|
||||
"path": "assets/links-to-official-docs.md",
|
||||
"sha256": "b0ff88175119eeaa2d60e1053f2f006cac4a94ad6a626f7a552a033761dc2424"
|
||||
}
|
||||
],
|
||||
"dirSha256": "0bc45f580a6e72fd7a2205584c2c2007615ac0ac5a1b62b6fd9b0d14f5ccc026"
|
||||
},
|
||||
"security": {
|
||||
"scannedAt": null,
|
||||
"scannerVersion": null,
|
||||
"flags": []
|
||||
}
|
||||
}
|
||||
816
references/common-errors.md
Normal file
816
references/common-errors.md
Normal file
@@ -0,0 +1,816 @@
|
||||
# TinaCMS Common Errors - Complete Reference
|
||||
|
||||
This document provides detailed troubleshooting for all 9 common TinaCMS errors.
|
||||
|
||||
**Last Updated**: 2025-10-24
|
||||
**TinaCMS Version**: 2.9.0
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [ESbuild Compilation Errors](#1-esbuild-compilation-errors)
|
||||
2. [Module Resolution Issues](#2-module-resolution-issues)
|
||||
3. [Field Naming Constraints](#3-field-naming-constraints)
|
||||
4. [Docker Binding Issues](#4-docker-binding-issues)
|
||||
5. [Missing _template Key Error](#5-missing-_template-key-error)
|
||||
6. [Path Mismatch Issues](#6-path-mismatch-issues)
|
||||
7. [Build Script Ordering Problems](#7-build-script-ordering-problems)
|
||||
8. [Failed Loading TinaCMS Assets](#8-failed-loading-tinacms-assets)
|
||||
9. [Reference Field 503 Service Unavailable](#9-reference-field-503-service-unavailable)
|
||||
|
||||
---
|
||||
|
||||
## 1. ESbuild Compilation Errors
|
||||
|
||||
### Error Messages
|
||||
|
||||
```
|
||||
ERROR: Schema Not Successfully Built
|
||||
ERROR: Config Not Successfully Executed
|
||||
ERROR: Cannot bundle code that requires custom loaders
|
||||
```
|
||||
|
||||
### Causes
|
||||
|
||||
The `tina/config.ts` file is compiled using esbuild and executed in Node.js. This can fail when:
|
||||
|
||||
1. **Importing code with custom loaders**
|
||||
- Webpack loaders
|
||||
- Babel plugins
|
||||
- PostCSS processors
|
||||
- SCSS/SASS files
|
||||
- Vue/Svelte components
|
||||
|
||||
2. **Importing frontend-only code**
|
||||
- Code using `window` object
|
||||
- Code using DOM APIs
|
||||
- React hooks outside components
|
||||
- Browser-specific APIs
|
||||
|
||||
3. **Importing entire component libraries**
|
||||
- `import { Component } from '../components/'` pulls in entire directory
|
||||
- May include dependencies not compatible with Node.js
|
||||
|
||||
### Solutions
|
||||
|
||||
#### Solution 1: Import Specific Files Only
|
||||
|
||||
```typescript
|
||||
// ❌ Bad - Imports entire directory
|
||||
import { HeroComponent } from '../components/'
|
||||
|
||||
// ✅ Good - Import specific file
|
||||
import { HeroComponent } from '../components/blocks/hero'
|
||||
|
||||
// ❌ Bad - Imports entire library
|
||||
import * as Components from '../components'
|
||||
|
||||
// ✅ Good - Import only what you need
|
||||
import { HeroComponent, CTAComponent } from '../components/blocks'
|
||||
```
|
||||
|
||||
#### Solution 2: Create Separate Schema Files
|
||||
|
||||
If your component definitions are complex, extract schema to separate files:
|
||||
|
||||
```typescript
|
||||
// components/hero/hero.schema.ts
|
||||
export const heroBlockSchema = {
|
||||
name: 'hero',
|
||||
label: 'Hero Section',
|
||||
fields: [/* ... */]
|
||||
}
|
||||
|
||||
// tina/config.ts
|
||||
import { heroBlockSchema } from '../components/hero/hero.schema'
|
||||
|
||||
export default defineConfig({
|
||||
schema: {
|
||||
collections: [{
|
||||
fields: [{
|
||||
type: 'object',
|
||||
name: 'hero',
|
||||
...heroBlockSchema
|
||||
}]
|
||||
}]
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
#### Solution 3: Use Type Imports Only
|
||||
|
||||
```typescript
|
||||
// ✅ Import only types (doesn't execute code)
|
||||
import type { HeroProps } from '../components/Hero'
|
||||
```
|
||||
|
||||
### Prevention
|
||||
|
||||
- Keep `tina/config.ts` minimal
|
||||
- Only import type definitions and simple utilities
|
||||
- Avoid importing UI components
|
||||
- Extract schema definitions to separate files
|
||||
- Test config builds with `npx @tinacms/cli@latest build`
|
||||
|
||||
### Related Links
|
||||
|
||||
- Official Docs: https://tina.io/docs/errors/esbuild-error
|
||||
- GitHub Issue: https://github.com/tinacms/tinacms/issues/3472
|
||||
|
||||
---
|
||||
|
||||
## 2. Module Resolution Issues
|
||||
|
||||
### Error Messages
|
||||
|
||||
```
|
||||
Error: Could not resolve "tinacms"
|
||||
Module not found: Can't resolve 'tinacms'
|
||||
Cannot find module 'tinacms' or its corresponding type declarations
|
||||
```
|
||||
|
||||
### Causes
|
||||
|
||||
1. **Corrupted Installation**
|
||||
- Incomplete `npm install`
|
||||
- Network issues during installation
|
||||
- Corrupted `node_modules`
|
||||
|
||||
2. **Version Mismatches**
|
||||
- Conflicting peer dependencies
|
||||
- React version mismatch
|
||||
- TinaCMS version incompatibility
|
||||
|
||||
3. **Missing Dependencies**
|
||||
- `react` and `react-dom` not installed (required even for non-React frameworks)
|
||||
- `@tinacms/cli` not in devDependencies
|
||||
|
||||
### Solutions
|
||||
|
||||
#### Solution 1: Clean Reinstall (npm)
|
||||
|
||||
```bash
|
||||
# Remove node_modules and lock file
|
||||
rm -rf node_modules package-lock.json
|
||||
|
||||
# Clear npm cache
|
||||
npm cache clean --force
|
||||
|
||||
# Reinstall all dependencies
|
||||
npm install
|
||||
```
|
||||
|
||||
#### Solution 2: Clean Reinstall (pnpm)
|
||||
|
||||
```bash
|
||||
# Remove node_modules and lock file
|
||||
rm -rf node_modules pnpm-lock.yaml
|
||||
|
||||
# Clear pnpm cache
|
||||
pnpm store prune
|
||||
|
||||
# Reinstall all dependencies
|
||||
pnpm install
|
||||
```
|
||||
|
||||
#### Solution 3: Clean Reinstall (yarn)
|
||||
|
||||
```bash
|
||||
# Remove node_modules and lock file
|
||||
rm -rf node_modules yarn.lock
|
||||
|
||||
# Clear yarn cache
|
||||
yarn cache clean
|
||||
|
||||
# Reinstall all dependencies
|
||||
yarn install
|
||||
```
|
||||
|
||||
#### Solution 4: Ensure React Dependencies (Non-React Frameworks)
|
||||
|
||||
Even if you're using Hugo, Jekyll, or Eleventy, you MUST install React:
|
||||
|
||||
```bash
|
||||
npm install react@^19 react-dom@^19
|
||||
```
|
||||
|
||||
### Prevention
|
||||
|
||||
- Use lockfiles (`package-lock.json`, `pnpm-lock.yaml`, `yarn.lock`)
|
||||
- Don't use `--no-optional` or `--omit=optional` flags
|
||||
- Ensure `react` and `react-dom` are in dependencies
|
||||
- Keep TinaCMS versions consistent across packages
|
||||
|
||||
### Related Links
|
||||
|
||||
- GitHub Issue: https://github.com/tinacms/tinacms/issues/4530
|
||||
|
||||
---
|
||||
|
||||
## 3. Field Naming Constraints
|
||||
|
||||
### Error Messages
|
||||
|
||||
```
|
||||
Field name contains invalid characters
|
||||
Invalid field name: 'hero-image'
|
||||
Field names must be alphanumeric
|
||||
```
|
||||
|
||||
### Causes
|
||||
|
||||
TinaCMS field names can ONLY contain:
|
||||
- Letters (a-z, A-Z)
|
||||
- Numbers (0-9)
|
||||
- Underscores (_)
|
||||
|
||||
**NOT allowed**:
|
||||
- Hyphens (-)
|
||||
- Spaces ( )
|
||||
- Special characters (!@#$%^&*)
|
||||
|
||||
This is a **breaking change** from Forestry.io which allowed hyphens.
|
||||
|
||||
### Solutions
|
||||
|
||||
#### Solution 1: Use Underscores
|
||||
|
||||
```typescript
|
||||
// ❌ Bad
|
||||
{
|
||||
name: 'hero-image',
|
||||
label: 'Hero Image',
|
||||
type: 'image'
|
||||
}
|
||||
|
||||
// ✅ Good
|
||||
{
|
||||
name: 'hero_image',
|
||||
label: 'Hero Image',
|
||||
type: 'image'
|
||||
}
|
||||
```
|
||||
|
||||
#### Solution 2: Use camelCase
|
||||
|
||||
```typescript
|
||||
// ✅ Also good
|
||||
{
|
||||
name: 'heroImage',
|
||||
label: 'Hero Image',
|
||||
type: 'image'
|
||||
}
|
||||
```
|
||||
|
||||
#### Solution 3: Migrate Existing Content
|
||||
|
||||
If migrating from Forestry.io with hyphenated field names:
|
||||
|
||||
```bash
|
||||
# Find all files with hyphenated frontmatter
|
||||
find content -name "*.md" -exec sed -i 's/hero-image:/hero_image:/g' {} +
|
||||
find content -name "*.md" -exec sed -i 's/cover-photo:/cover_photo:/g' {} +
|
||||
```
|
||||
|
||||
### Prevention
|
||||
|
||||
- Use underscores or camelCase for all field names
|
||||
- Document naming convention in team style guide
|
||||
- Use linter/validator to catch invalid names
|
||||
|
||||
### Related Links
|
||||
|
||||
- Migration Docs: https://tina.io/docs/forestry/migrate#field-names
|
||||
|
||||
---
|
||||
|
||||
## 4. Docker Binding Issues
|
||||
|
||||
### Error Messages
|
||||
|
||||
```
|
||||
Connection refused: http://localhost:3000
|
||||
Cannot connect to TinaCMS admin interface
|
||||
ERR_CONNECTION_REFUSED
|
||||
```
|
||||
|
||||
### Causes
|
||||
|
||||
By default, development servers bind to `127.0.0.1` (localhost only), which prevents access from:
|
||||
- Docker containers
|
||||
- Network devices
|
||||
- Remote connections
|
||||
|
||||
### Solutions
|
||||
|
||||
#### Solution 1: Next.js
|
||||
|
||||
```bash
|
||||
tinacms dev -c "next dev --hostname 0.0.0.0"
|
||||
```
|
||||
|
||||
Or in `package.json`:
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"dev": "tinacms dev -c \"next dev --hostname 0.0.0.0\""
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Solution 2: Vite
|
||||
|
||||
```bash
|
||||
tinacms dev -c "vite --host 0.0.0.0"
|
||||
```
|
||||
|
||||
Or in `vite.config.ts`:
|
||||
```typescript
|
||||
export default defineConfig({
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
port: 3000
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
#### Solution 3: Astro
|
||||
|
||||
```bash
|
||||
tinacms dev -c "astro dev --host 0.0.0.0"
|
||||
```
|
||||
|
||||
Or in `astro.config.mjs`:
|
||||
```javascript
|
||||
export default defineConfig({
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
port: 4321
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
#### Solution 4: Docker Compose
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
services:
|
||||
app:
|
||||
build: .
|
||||
ports:
|
||||
- "3000:3000"
|
||||
command: npm run dev # Must include --hostname 0.0.0.0
|
||||
```
|
||||
|
||||
### Prevention
|
||||
|
||||
- Always use `--host 0.0.0.0` in Docker environments
|
||||
- Configure framework config files instead of CLI flags
|
||||
- Document binding requirements in README
|
||||
|
||||
---
|
||||
|
||||
## 5. Missing `_template` Key Error
|
||||
|
||||
### Error Messages
|
||||
|
||||
```
|
||||
GetCollection failed: Unable to fetch
|
||||
template name was not provided
|
||||
Error: Document missing _template field
|
||||
```
|
||||
|
||||
### Causes
|
||||
|
||||
When a collection uses `templates` array (multiple schemas), each document **must** include a `_template` field in its frontmatter specifying which template to use.
|
||||
|
||||
### Solutions
|
||||
|
||||
#### Solution 1: Use `fields` Instead (Recommended)
|
||||
|
||||
If you only have one schema, use `fields` instead of `templates`:
|
||||
|
||||
```typescript
|
||||
// ❌ Using templates (requires _template in frontmatter)
|
||||
{
|
||||
name: 'post',
|
||||
path: 'content/posts',
|
||||
templates: [
|
||||
{
|
||||
name: 'article',
|
||||
fields: [/* ... */]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// ✅ Using fields (no _template needed)
|
||||
{
|
||||
name: 'post',
|
||||
path: 'content/posts',
|
||||
fields: [/* ... */] // Same fields as above
|
||||
}
|
||||
```
|
||||
|
||||
#### Solution 2: Add `_template` to Frontmatter
|
||||
|
||||
If you need multiple templates, ensure all documents have `_template`:
|
||||
|
||||
```yaml
|
||||
---
|
||||
_template: article
|
||||
title: My Blog Post
|
||||
date: 2025-10-24
|
||||
---
|
||||
|
||||
Content here...
|
||||
```
|
||||
|
||||
#### Solution 3: Batch Update Existing Files
|
||||
|
||||
```bash
|
||||
# Add _template field to all files
|
||||
find content/posts -name "*.md" -exec sed -i '1a _template: article' {} +
|
||||
```
|
||||
|
||||
#### Solution 4: Set Default Template
|
||||
|
||||
```typescript
|
||||
{
|
||||
name: 'post',
|
||||
path: 'content/posts',
|
||||
templates: [
|
||||
{
|
||||
name: 'article',
|
||||
fields: [/* ... */]
|
||||
}
|
||||
],
|
||||
ui: {
|
||||
defaultItem: () => ({
|
||||
_template: 'article' // Default template for new documents
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Prevention
|
||||
|
||||
- Use `fields` for single-schema collections
|
||||
- Use `templates` only when you truly need multiple schemas
|
||||
- Document `_template` requirement in team guidelines
|
||||
|
||||
---
|
||||
|
||||
## 6. Path Mismatch Issues
|
||||
|
||||
### Error Messages
|
||||
|
||||
```
|
||||
No files found in collection
|
||||
File not found: content/posts/hello.md
|
||||
GraphQL query returned empty results
|
||||
```
|
||||
|
||||
### Causes
|
||||
|
||||
The `path` in your collection config doesn't match where files actually live.
|
||||
|
||||
### Solutions
|
||||
|
||||
#### Solution 1: Verify File Locations
|
||||
|
||||
```bash
|
||||
# Check where your files actually are
|
||||
ls -R content/
|
||||
|
||||
# If files are in content/posts/
|
||||
# Your config should be:
|
||||
{
|
||||
name: 'post',
|
||||
path: 'content/posts' // ✅ Matches file location
|
||||
}
|
||||
```
|
||||
|
||||
#### Solution 2: No Trailing Slash
|
||||
|
||||
```typescript
|
||||
// ❌ Bad
|
||||
{
|
||||
path: 'content/posts/' // Trailing slash may cause issues
|
||||
}
|
||||
|
||||
// ✅ Good
|
||||
{
|
||||
path: 'content/posts' // No trailing slash
|
||||
}
|
||||
```
|
||||
|
||||
#### Solution 3: Use Relative Paths
|
||||
|
||||
```typescript
|
||||
// ✅ Path relative to project root
|
||||
{
|
||||
path: 'content/posts'
|
||||
}
|
||||
|
||||
// ❌ Don't use absolute paths
|
||||
{
|
||||
path: '/home/user/project/content/posts'
|
||||
}
|
||||
```
|
||||
|
||||
#### Solution 4: Audit Paths
|
||||
|
||||
```bash
|
||||
# TinaCMS CLI includes audit command
|
||||
npx @tinacms/cli@latest audit
|
||||
|
||||
# Shows which files match collections
|
||||
# Helps identify path mismatches
|
||||
```
|
||||
|
||||
### Prevention
|
||||
|
||||
- Always verify file structure before setting `path`
|
||||
- Use `npx @tinacms/cli@latest audit` to check paths
|
||||
- Document expected file structure in README
|
||||
|
||||
---
|
||||
|
||||
## 7. Build Script Ordering Problems
|
||||
|
||||
### Error Messages
|
||||
|
||||
```
|
||||
ERROR: Cannot find module '../tina/__generated__/client'
|
||||
ERROR: Property 'queries' does not exist on type '{}'
|
||||
Type error: Module '../tina/__generated__/client' has no exported member 'client'
|
||||
```
|
||||
|
||||
### Causes
|
||||
|
||||
Framework build runs before `tinacms build`, so generated TypeScript types don't exist yet.
|
||||
|
||||
### Solutions
|
||||
|
||||
#### Solution 1: Correct Script Order
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"build": "tinacms build && next build" // ✅ Tina FIRST
|
||||
// NOT: "build": "next build && tinacms build" // ❌ Wrong
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Solution 2: CI/CD Fix (GitHub Actions)
|
||||
|
||||
```yaml
|
||||
name: Build
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
|
||||
- name: Build Tina
|
||||
run: npx @tinacms/cli@latest build # Generate types FIRST
|
||||
|
||||
- name: Build App
|
||||
run: npm run build # Now framework build works
|
||||
```
|
||||
|
||||
#### Solution 3: Vercel Configuration
|
||||
|
||||
```json
|
||||
// vercel.json
|
||||
{
|
||||
"buildCommand": "tinacms build && next build"
|
||||
}
|
||||
```
|
||||
|
||||
#### Solution 4: Netlify Configuration
|
||||
|
||||
```toml
|
||||
# netlify.toml
|
||||
[build]
|
||||
command = "tinacms build && npm run build"
|
||||
```
|
||||
|
||||
### Prevention
|
||||
|
||||
- Always run `tinacms build` before framework build
|
||||
- Document correct build order in README
|
||||
- Add build order to CI/CD templates
|
||||
|
||||
---
|
||||
|
||||
## 8. Failed Loading TinaCMS Assets
|
||||
|
||||
### Error Messages
|
||||
|
||||
```
|
||||
Failed to load resource: net::ERR_CONNECTION_REFUSED
|
||||
http://localhost:4001/...
|
||||
Mixed Content: The page was loaded over HTTPS, but requested an insecure resource
|
||||
```
|
||||
|
||||
### Causes
|
||||
|
||||
1. **Development `admin/index.html` pushed to production**
|
||||
- Dev version loads assets from `localhost:4001`
|
||||
- Production needs built assets
|
||||
|
||||
2. **Subdirectory deployment without `basePath` config**
|
||||
- Site deployed to `example.com/blog/`
|
||||
- TinaCMS tries to load from `example.com/admin/` (fails)
|
||||
|
||||
### Solutions
|
||||
|
||||
#### Solution 1: Always Build for Production
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"build": "tinacms build && next build" // ✅ Always build
|
||||
// NOT: "build": "tinacms dev" // ❌ Never dev in prod
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Solution 2: Configure `basePath` for Subdirectories
|
||||
|
||||
If site is at `example.com/blog/`:
|
||||
|
||||
```typescript
|
||||
// tina/config.ts
|
||||
export default defineConfig({
|
||||
build: {
|
||||
outputFolder: 'admin',
|
||||
publicFolder: 'public',
|
||||
basePath: 'blog' // ← Subdirectory path
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
#### Solution 3: Verify .gitignore
|
||||
|
||||
```gitignore
|
||||
# .gitignore
|
||||
|
||||
# TinaCMS
|
||||
.tina/__generated__
|
||||
admin/index.html # ← Don't commit dev admin
|
||||
|
||||
# But DO commit:
|
||||
# public/admin/ (production build)
|
||||
```
|
||||
|
||||
#### Solution 4: CI/CD Always Build
|
||||
|
||||
```yaml
|
||||
# GitHub Actions
|
||||
- run: npx @tinacms/cli@latest build # Not dev!
|
||||
```
|
||||
|
||||
### Prevention
|
||||
|
||||
- Use `tinacms build` in all CI/CD
|
||||
- Set `basePath` for subdirectory deploys
|
||||
- Don't commit `admin/index.html` from dev mode
|
||||
|
||||
---
|
||||
|
||||
## 9. Reference Field 503 Service Unavailable
|
||||
|
||||
### Error Messages
|
||||
|
||||
```
|
||||
503 Service Unavailable
|
||||
Request timed out
|
||||
Reference field dropdown not loading
|
||||
```
|
||||
|
||||
### Causes
|
||||
|
||||
Reference fields load ALL documents from the referenced collection. If the collection has 100s or 1000s of items, the request times out.
|
||||
|
||||
**Current Limitation**: TinaCMS does not support pagination for reference fields (as of v2.9.0).
|
||||
|
||||
### Solutions
|
||||
|
||||
#### Solution 1: Split Large Collections
|
||||
|
||||
Instead of one huge collection, split by category:
|
||||
|
||||
```typescript
|
||||
// ❌ Single huge collection (1000+ authors)
|
||||
{
|
||||
name: 'author',
|
||||
path: 'content/authors',
|
||||
fields: [/* ... */]
|
||||
}
|
||||
|
||||
// ✅ Split into manageable sizes
|
||||
{
|
||||
name: 'active_author',
|
||||
label: 'Active Authors',
|
||||
path: 'content/authors/active',
|
||||
fields: [/* ... */]
|
||||
}
|
||||
|
||||
{
|
||||
name: 'archived_author',
|
||||
label: 'Archived Authors',
|
||||
path: 'content/authors/archived',
|
||||
fields: [/* ... */]
|
||||
}
|
||||
```
|
||||
|
||||
#### Solution 2: Use String Field with Validation
|
||||
|
||||
Instead of reference, use a string select:
|
||||
|
||||
```typescript
|
||||
// ❌ Reference field (times out with 100+ authors)
|
||||
{
|
||||
type: 'reference',
|
||||
name: 'author',
|
||||
collections: ['author']
|
||||
}
|
||||
|
||||
// ✅ String field with curated options
|
||||
{
|
||||
type: 'string',
|
||||
name: 'author_id',
|
||||
label: 'Author',
|
||||
options: [
|
||||
{ label: 'John Doe', value: 'john-doe' },
|
||||
{ label: 'Jane Smith', value: 'jane-smith' },
|
||||
// ... curated list of ~50 authors max
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### Solution 3: Custom Field Component (Advanced)
|
||||
|
||||
Create a custom field component with client-side pagination:
|
||||
|
||||
```typescript
|
||||
// Advanced: Requires custom React component
|
||||
{
|
||||
type: 'string',
|
||||
name: 'author_id',
|
||||
ui: {
|
||||
component: 'author-select-paginated' // Your custom component
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
See: https://tina.io/docs/extending-tina/custom-field-components/
|
||||
|
||||
#### Solution 4: Use External Service
|
||||
|
||||
For very large datasets, consider:
|
||||
- Store authors in D1/KV
|
||||
- Use custom field component to query via API
|
||||
- Load data on-demand with search/filter
|
||||
|
||||
### Prevention
|
||||
|
||||
- Limit referenced collections to <100 items
|
||||
- Use string fields for large datasets
|
||||
- Consider alternative architectures for huge collections
|
||||
|
||||
### Related Links
|
||||
|
||||
- GitHub Issue: https://github.com/tinacms/tinacms/issues/3821
|
||||
- Custom Fields Docs: https://tina.io/docs/extending-tina/custom-field-components/
|
||||
|
||||
---
|
||||
|
||||
## Additional Resources
|
||||
|
||||
### Official Documentation
|
||||
- **Main Docs**: https://tina.io/docs
|
||||
- **Error FAQ**: https://tina.io/docs/errors/faq
|
||||
- **ESbuild Error**: https://tina.io/docs/errors/esbuild-error
|
||||
- **Contributing**: https://tina.io/docs/contributing/troubleshooting
|
||||
|
||||
### Support
|
||||
- **Discord**: https://discord.gg/zumN63Ybpf
|
||||
- **GitHub Issues**: https://github.com/tinacms/tinacms/issues
|
||||
- **GitHub Discussions**: https://github.com/tinacms/tinacms/discussions
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: 2025-10-24
|
||||
**Errors Documented**: 9
|
||||
**Prevention Rate**: 100%
|
||||
92
scripts/check-versions.sh
Executable file
92
scripts/check-versions.sh
Executable file
@@ -0,0 +1,92 @@
|
||||
#!/bin/bash
|
||||
|
||||
##
|
||||
# TinaCMS Version Checker
|
||||
#
|
||||
# Checks if you're using current versions of TinaCMS packages
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/check-versions.sh
|
||||
#
|
||||
# Last Updated: 2025-10-24
|
||||
##
|
||||
|
||||
set -e
|
||||
|
||||
echo "🔍 Checking TinaCMS package versions..."
|
||||
echo ""
|
||||
|
||||
# Colors
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Expected versions (update these when new versions released)
|
||||
EXPECTED_TINACMS="2.9.0"
|
||||
EXPECTED_CLI="1.11.0"
|
||||
EXPECTED_REACT="19.0.0"
|
||||
|
||||
# Function to get installed version
|
||||
get_installed_version() {
|
||||
local package=$1
|
||||
if [ -f "package.json" ]; then
|
||||
node -e "try { const pkg = require('./package.json'); console.log(pkg.dependencies['$package'] || pkg.devDependencies['$package'] || 'not-installed'); } catch(e) { console.log('not-installed'); }" 2>/dev/null
|
||||
else
|
||||
echo "no-package-json"
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to get latest version from npm
|
||||
get_latest_version() {
|
||||
local package=$1
|
||||
npm view "$package" version 2>/dev/null || echo "unknown"
|
||||
}
|
||||
|
||||
# Function to compare versions
|
||||
check_version() {
|
||||
local package=$1
|
||||
local expected=$2
|
||||
local installed=$(get_installed_version "$package")
|
||||
local latest=$(get_latest_version "$package")
|
||||
|
||||
echo "📦 $package"
|
||||
echo " Expected: $expected"
|
||||
echo " Installed: $installed"
|
||||
echo " Latest: $latest"
|
||||
|
||||
if [ "$installed" = "not-installed" ]; then
|
||||
echo -e " ${RED}❌ NOT INSTALLED${NC}"
|
||||
elif [ "$installed" = "no-package-json" ]; then
|
||||
echo -e " ${YELLOW}⚠️ No package.json found${NC}"
|
||||
elif [ "$installed" = "^$expected" ] || [ "$installed" = "~$expected" ] || [ "$installed" = "$expected" ]; then
|
||||
echo -e " ${GREEN}✅ Current version${NC}"
|
||||
else
|
||||
echo -e " ${YELLOW}⚠️ Version mismatch - consider upgrading${NC}"
|
||||
fi
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Check if we're in a project directory
|
||||
if [ ! -f "package.json" ]; then
|
||||
echo -e "${YELLOW}⚠️ No package.json found in current directory${NC}"
|
||||
echo "Run this script from your project root."
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Check versions
|
||||
check_version "tinacms" "$EXPECTED_TINACMS"
|
||||
check_version "@tinacms/cli" "$EXPECTED_CLI"
|
||||
check_version "react" "$EXPECTED_REACT"
|
||||
check_version "react-dom" "$EXPECTED_REACT"
|
||||
|
||||
echo "✅ Version check complete!"
|
||||
echo ""
|
||||
echo "To upgrade:"
|
||||
echo " npm install tinacms@latest @tinacms/cli@latest"
|
||||
echo " npm install react@latest react-dom@latest"
|
||||
echo ""
|
||||
echo "To check latest versions manually:"
|
||||
echo " npm view tinacms version"
|
||||
echo " npm view @tinacms/cli version"
|
||||
echo ""
|
||||
17
templates/astro/.env.example
Normal file
17
templates/astro/.env.example
Normal file
@@ -0,0 +1,17 @@
|
||||
# TinaCloud Credentials
|
||||
# Get these from https://app.tina.io after creating a project
|
||||
|
||||
# Note: Astro requires PUBLIC_ prefix for client-exposed variables
|
||||
|
||||
# Client ID (public, safe to expose)
|
||||
PUBLIC_TINA_CLIENT_ID=your_client_id_here
|
||||
|
||||
# Read-only token (keep secret, NOT public)
|
||||
# This should not have PUBLIC_ prefix as it's server-side only
|
||||
TINA_TOKEN=your_read_only_token_here
|
||||
|
||||
# Git branch
|
||||
PUBLIC_GITHUB_BRANCH=main
|
||||
|
||||
# For self-hosted backend
|
||||
# PUBLIC_TINA_API_URL=/api/tina/gql
|
||||
29
templates/astro/astro.config.mjs
Normal file
29
templates/astro/astro.config.mjs
Normal file
@@ -0,0 +1,29 @@
|
||||
import { defineConfig } from 'astro/config'
|
||||
import react from '@astro/react'
|
||||
import mdx from '@astro/mdx'
|
||||
|
||||
/**
|
||||
* Astro Configuration for TinaCMS
|
||||
*
|
||||
* Key settings:
|
||||
* - React integration (required for Tina admin interface)
|
||||
* - MDX support (for rich content)
|
||||
* - Site configuration
|
||||
*
|
||||
* For more info: https://astro.build/config
|
||||
*/
|
||||
export default defineConfig({
|
||||
integrations: [
|
||||
react(), // Required for TinaCMS admin
|
||||
mdx(), // Recommended for rich content
|
||||
],
|
||||
|
||||
// Your site URL (for sitemap, canonical URLs, etc.)
|
||||
site: 'https://example.com',
|
||||
|
||||
// Optional: Server configuration
|
||||
server: {
|
||||
port: 4321,
|
||||
host: '0.0.0.0', // Allows external connections (Docker, network)
|
||||
},
|
||||
})
|
||||
25
templates/astro/package.json
Normal file
25
templates/astro/package.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "astro-tinacms-app",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "tinacms dev -c \"astro dev\"",
|
||||
"build": "tinacms build && astro build",
|
||||
"preview": "astro preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/mdx": "^4.0.0",
|
||||
"@astrojs/react": "^4.0.0",
|
||||
"astro": "^5.15.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"tinacms": "^2.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tinacms/cli": "^1.11.0",
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"typescript": "^5.6.0"
|
||||
}
|
||||
}
|
||||
65
templates/astro/tina-config.ts
Normal file
65
templates/astro/tina-config.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { defineConfig } from 'tinacms'
|
||||
import { blogPostCollection } from './collections/blog-post'
|
||||
import { docPageCollection } from './collections/doc-page'
|
||||
import { authorCollection } from './collections/author'
|
||||
|
||||
/**
|
||||
* TinaCMS Configuration for Astro
|
||||
*
|
||||
* This config works with Astro static site generator.
|
||||
*
|
||||
* Key Points:
|
||||
* - Visual editing is experimental (requires React components)
|
||||
* - Best for content-focused static sites
|
||||
* - Environment variables use PUBLIC_ prefix
|
||||
* - Admin interface requires React integration
|
||||
*
|
||||
* Usage:
|
||||
* 1. Copy this file to: tina/config.ts
|
||||
* 2. Copy collection files to: tina/collections/
|
||||
* 3. Set environment variables in .env
|
||||
* 4. Run: npm run dev
|
||||
* 5. Access admin: http://localhost:4321/admin/index.html
|
||||
*/
|
||||
|
||||
// Get Git branch from environment
|
||||
const branch =
|
||||
process.env.PUBLIC_GITHUB_BRANCH ||
|
||||
process.env.PUBLIC_VERCEL_GIT_COMMIT_REF ||
|
||||
'main'
|
||||
|
||||
export default defineConfig({
|
||||
// Git branch to use
|
||||
branch,
|
||||
|
||||
// TinaCloud credentials
|
||||
// Note: Astro requires PUBLIC_ prefix for client-exposed variables
|
||||
clientId: process.env.PUBLIC_TINA_CLIENT_ID,
|
||||
token: process.env.TINA_TOKEN,
|
||||
|
||||
// Build configuration
|
||||
build: {
|
||||
outputFolder: 'admin',
|
||||
publicFolder: 'public',
|
||||
},
|
||||
|
||||
// Media configuration
|
||||
media: {
|
||||
tina: {
|
||||
mediaRoot: 'uploads',
|
||||
publicFolder: 'public',
|
||||
},
|
||||
},
|
||||
|
||||
// Content schema
|
||||
schema: {
|
||||
collections: [
|
||||
blogPostCollection,
|
||||
authorCollection,
|
||||
docPageCollection,
|
||||
],
|
||||
},
|
||||
|
||||
// Optional: Self-hosted backend
|
||||
// contentApiUrlOverride: '/api/tina/gql',
|
||||
})
|
||||
148
templates/cloudflare-worker-backend/src/index.ts
Normal file
148
templates/cloudflare-worker-backend/src/index.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
/**
|
||||
* Cloudflare Worker Backend for TinaCMS
|
||||
*
|
||||
* This Worker provides a self-hosted TinaCMS backend on Cloudflare's edge network.
|
||||
*
|
||||
* Features:
|
||||
* - GraphQL API for content management
|
||||
* - Authentication via Auth.js or custom providers
|
||||
* - Local development mode
|
||||
* - Global edge deployment
|
||||
*
|
||||
* Setup:
|
||||
* 1. Install dependencies: npm install @tinacms/datalayer tinacms-authjs
|
||||
* 2. Generate database client: npx @tinacms/cli@latest init backend
|
||||
* 3. Configure wrangler.jsonc
|
||||
* 4. Deploy: npx wrangler deploy
|
||||
*
|
||||
* Environment Variables:
|
||||
* - TINA_PUBLIC_IS_LOCAL: "true" for local dev, "false" for production
|
||||
* - NEXTAUTH_SECRET: Secret key for Auth.js (production only)
|
||||
*/
|
||||
|
||||
import { TinaNodeBackend, LocalBackendAuthProvider } from '@tinacms/datalayer'
|
||||
import { AuthJsBackendAuthProvider, TinaAuthJSOptions } from 'tinacms-authjs'
|
||||
// @ts-ignore - Generated by TinaCMS CLI
|
||||
import databaseClient from '../../tina/__generated__/databaseClient'
|
||||
|
||||
/**
|
||||
* Cloudflare Workers Environment
|
||||
*
|
||||
* Extend this interface to add your environment variables and bindings
|
||||
*/
|
||||
interface Env {
|
||||
// Environment variables
|
||||
TINA_PUBLIC_IS_LOCAL?: string
|
||||
NEXTAUTH_SECRET?: string
|
||||
|
||||
// Optional: KV namespace for session storage
|
||||
// SESSION_KV?: KVNamespace
|
||||
|
||||
// Optional: D1 database for user management
|
||||
// DB?: D1Database
|
||||
}
|
||||
|
||||
/**
|
||||
* Main Worker Handler
|
||||
*
|
||||
* Processes incoming requests and routes them to the TinaCMS backend
|
||||
*/
|
||||
export default {
|
||||
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
|
||||
// Determine if we're in local development mode
|
||||
const isLocal = env.TINA_PUBLIC_IS_LOCAL === 'true'
|
||||
|
||||
// Initialize TinaCMS backend
|
||||
const handler = TinaNodeBackend({
|
||||
authProvider: isLocal
|
||||
? LocalBackendAuthProvider()
|
||||
: AuthJsBackendAuthProvider({
|
||||
authOptions: TinaAuthJSOptions({
|
||||
databaseClient,
|
||||
secret: env.NEXTAUTH_SECRET || '',
|
||||
// Add OAuth providers here:
|
||||
// providers: [
|
||||
// DiscordProvider({
|
||||
// clientId: env.DISCORD_CLIENT_ID,
|
||||
// clientSecret: env.DISCORD_CLIENT_SECRET,
|
||||
// }),
|
||||
// ],
|
||||
}),
|
||||
}),
|
||||
databaseClient,
|
||||
})
|
||||
|
||||
try {
|
||||
// Handle CORS preflight requests
|
||||
if (request.method === 'OPTIONS') {
|
||||
return new Response(null, {
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Process the request through TinaCMS
|
||||
const response = await handler(request)
|
||||
|
||||
// Add CORS headers to response
|
||||
const corsResponse = new Response(response.body, response)
|
||||
corsResponse.headers.set('Access-Control-Allow-Origin': '*')
|
||||
corsResponse.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
|
||||
corsResponse.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization')
|
||||
|
||||
return corsResponse
|
||||
} catch (error) {
|
||||
console.error('TinaCMS Backend Error:', error)
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: 'Internal Server Error',
|
||||
message: error instanceof Error ? error.message : 'Unknown error',
|
||||
}),
|
||||
{
|
||||
status: 500,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Alternative: Hono-based Implementation (Recommended)
|
||||
*
|
||||
* For more flexibility, use Hono framework:
|
||||
*
|
||||
* import { Hono } from 'hono'
|
||||
* import { cors } from 'hono/cors'
|
||||
*
|
||||
* const app = new Hono()
|
||||
*
|
||||
* app.use('/*', cors())
|
||||
*
|
||||
* app.all('/api/tina/*', async (c) => {
|
||||
* const isLocal = c.env.TINA_PUBLIC_IS_LOCAL === 'true'
|
||||
*
|
||||
* const handler = TinaNodeBackend({
|
||||
* authProvider: isLocal
|
||||
* ? LocalBackendAuthProvider()
|
||||
* : AuthJsBackendAuthProvider({
|
||||
* authOptions: TinaAuthJSOptions({
|
||||
* databaseClient,
|
||||
* secret: c.env.NEXTAUTH_SECRET,
|
||||
* }),
|
||||
* }),
|
||||
* databaseClient,
|
||||
* })
|
||||
*
|
||||
* return handler(c.req.raw)
|
||||
* })
|
||||
*
|
||||
* export default app
|
||||
*/
|
||||
55
templates/cloudflare-worker-backend/wrangler.jsonc
Normal file
55
templates/cloudflare-worker-backend/wrangler.jsonc
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/cloudflare/wrangler/main/npm/wrangler/wrangler-schema.json",
|
||||
"name": "tina-backend",
|
||||
"main": "src/index.ts",
|
||||
"compatibility_date": "2025-10-24",
|
||||
"compatibility_flags": ["nodejs_compat"],
|
||||
|
||||
// Environment variables for development
|
||||
"vars": {
|
||||
"TINA_PUBLIC_IS_LOCAL": "false"
|
||||
},
|
||||
|
||||
// Environment-specific configurations
|
||||
"env": {
|
||||
"development": {
|
||||
"vars": {
|
||||
"TINA_PUBLIC_IS_LOCAL": "true"
|
||||
}
|
||||
},
|
||||
"production": {
|
||||
"vars": {
|
||||
"TINA_PUBLIC_IS_LOCAL": "false"
|
||||
},
|
||||
// Add secrets via: wrangler secret put NEXTAUTH_SECRET
|
||||
"secrets": [
|
||||
"NEXTAUTH_SECRET"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
// Optional: Add KV for session storage
|
||||
// "kv_namespaces": [
|
||||
// {
|
||||
// "binding": "SESSION_KV",
|
||||
// "id": "your-kv-namespace-id"
|
||||
// }
|
||||
// ],
|
||||
|
||||
// Optional: Add D1 for user management
|
||||
// "d1_databases": [
|
||||
// {
|
||||
// "binding": "DB",
|
||||
// "database_name": "tina-users",
|
||||
// "database_id": "your-d1-database-id"
|
||||
// }
|
||||
// ],
|
||||
|
||||
// Routes configuration (adjust based on your setup)
|
||||
"routes": [
|
||||
{
|
||||
"pattern": "yourdomain.com/api/tina/*",
|
||||
"zone_name": "yourdomain.com"
|
||||
}
|
||||
]
|
||||
}
|
||||
127
templates/collections/author.ts
Normal file
127
templates/collections/author.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import type { Collection } from 'tinacms'
|
||||
|
||||
/**
|
||||
* Author Collection Template
|
||||
*
|
||||
* A complete author schema for blog post references:
|
||||
* - Name, email
|
||||
* - Avatar image
|
||||
* - Bio
|
||||
* - Social media links
|
||||
*
|
||||
* Usage:
|
||||
* import { authorCollection } from './collections/author'
|
||||
*
|
||||
* export default defineConfig({
|
||||
* schema: {
|
||||
* collections: [authorCollection, blogPostCollection]
|
||||
* }
|
||||
* })
|
||||
*
|
||||
* Then reference in blog posts:
|
||||
* {
|
||||
* type: 'reference',
|
||||
* name: 'author',
|
||||
* collections: ['author']
|
||||
* }
|
||||
*/
|
||||
export const authorCollection: Collection = {
|
||||
name: 'author',
|
||||
label: 'Authors',
|
||||
path: 'content/authors',
|
||||
format: 'json',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
isTitle: true,
|
||||
required: true,
|
||||
description: 'Author\'s full name',
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'email',
|
||||
label: 'Email',
|
||||
required: true,
|
||||
ui: {
|
||||
validate: (value) => {
|
||||
if (!value) {
|
||||
return 'Email is required'
|
||||
}
|
||||
if (!value.includes('@')) {
|
||||
return 'Invalid email address'
|
||||
}
|
||||
},
|
||||
},
|
||||
description: 'Contact email address',
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'role',
|
||||
label: 'Role',
|
||||
description: 'Job title or role (e.g., "Senior Developer", "Content Writer")',
|
||||
},
|
||||
{
|
||||
type: 'image',
|
||||
name: 'avatar',
|
||||
label: 'Avatar',
|
||||
description: 'Profile picture (square, 400x400px recommended)',
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'bio',
|
||||
label: 'Bio',
|
||||
ui: {
|
||||
component: 'textarea',
|
||||
},
|
||||
description: 'Short biography (150-200 characters)',
|
||||
},
|
||||
{
|
||||
type: 'object',
|
||||
name: 'social',
|
||||
label: 'Social Media Links',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'twitter',
|
||||
label: 'Twitter',
|
||||
description: 'Twitter/X username (without @)',
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'github',
|
||||
label: 'GitHub',
|
||||
description: 'GitHub username',
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'linkedin',
|
||||
label: 'LinkedIn',
|
||||
description: 'LinkedIn profile URL',
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'website',
|
||||
label: 'Personal Website',
|
||||
description: 'Full URL to personal website',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'boolean',
|
||||
name: 'active',
|
||||
label: 'Active Author',
|
||||
required: true,
|
||||
description: 'Uncheck to hide author from listings',
|
||||
ui: {
|
||||
component: 'toggle',
|
||||
},
|
||||
},
|
||||
],
|
||||
ui: {
|
||||
defaultItem: () => ({
|
||||
active: true,
|
||||
}),
|
||||
},
|
||||
}
|
||||
146
templates/collections/blog-post.ts
Normal file
146
templates/collections/blog-post.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
import type { Collection } from 'tinacms'
|
||||
|
||||
/**
|
||||
* Blog Post Collection Template
|
||||
*
|
||||
* A complete blog post schema with:
|
||||
* - Title, excerpt, cover image
|
||||
* - Author reference
|
||||
* - Published date
|
||||
* - Draft status
|
||||
* - Rich-text body content
|
||||
*
|
||||
* Usage:
|
||||
* import { blogPostCollection } from './collections/blog-post'
|
||||
*
|
||||
* export default defineConfig({
|
||||
* schema: {
|
||||
* collections: [blogPostCollection]
|
||||
* }
|
||||
* })
|
||||
*/
|
||||
export const blogPostCollection: Collection = {
|
||||
name: 'post',
|
||||
label: 'Blog Posts',
|
||||
path: 'content/posts',
|
||||
format: 'mdx',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'title',
|
||||
label: 'Title',
|
||||
isTitle: true,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'excerpt',
|
||||
label: 'Excerpt',
|
||||
ui: {
|
||||
component: 'textarea',
|
||||
},
|
||||
description: 'Short summary shown in post listings (150-200 characters)',
|
||||
},
|
||||
{
|
||||
type: 'image',
|
||||
name: 'coverImage',
|
||||
label: 'Cover Image',
|
||||
description: 'Main image shown at top of post and in listings',
|
||||
},
|
||||
{
|
||||
type: 'datetime',
|
||||
name: 'date',
|
||||
label: 'Published Date',
|
||||
required: true,
|
||||
ui: {
|
||||
dateFormat: 'YYYY-MM-DD',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'reference',
|
||||
name: 'author',
|
||||
label: 'Author',
|
||||
collections: ['author'],
|
||||
description: 'Select the author of this post',
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'category',
|
||||
label: 'Category',
|
||||
options: [
|
||||
{ label: 'Technology', value: 'technology' },
|
||||
{ label: 'Design', value: 'design' },
|
||||
{ label: 'Business', value: 'business' },
|
||||
{ label: 'Tutorials', value: 'tutorials' },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'tags',
|
||||
label: 'Tags',
|
||||
list: true,
|
||||
description: 'Add tags for post categorization and search',
|
||||
},
|
||||
{
|
||||
type: 'boolean',
|
||||
name: 'draft',
|
||||
label: 'Draft',
|
||||
required: true,
|
||||
description: 'If checked, post will not be published',
|
||||
ui: {
|
||||
component: 'toggle',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'boolean',
|
||||
name: 'featured',
|
||||
label: 'Featured Post',
|
||||
description: 'Show this post prominently on homepage',
|
||||
},
|
||||
{
|
||||
type: 'object',
|
||||
name: 'seo',
|
||||
label: 'SEO Metadata',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'metaTitle',
|
||||
label: 'Meta Title',
|
||||
description: 'SEO title (leave blank to use post title)',
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'metaDescription',
|
||||
label: 'Meta Description',
|
||||
ui: {
|
||||
component: 'textarea',
|
||||
},
|
||||
description: 'SEO description (150-160 characters)',
|
||||
},
|
||||
{
|
||||
type: 'image',
|
||||
name: 'ogImage',
|
||||
label: 'Open Graph Image',
|
||||
description: 'Image for social media sharing (1200x630px)',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'rich-text',
|
||||
name: 'body',
|
||||
label: 'Body',
|
||||
isBody: true,
|
||||
},
|
||||
],
|
||||
ui: {
|
||||
router: ({ document }) => {
|
||||
return `/blog/${document._sys.filename}`
|
||||
},
|
||||
defaultItem: () => ({
|
||||
title: 'New Post',
|
||||
date: new Date().toISOString(),
|
||||
draft: true,
|
||||
featured: false,
|
||||
}),
|
||||
},
|
||||
}
|
||||
167
templates/collections/doc-page.ts
Normal file
167
templates/collections/doc-page.ts
Normal file
@@ -0,0 +1,167 @@
|
||||
import type { Collection } from 'tinacms'
|
||||
|
||||
/**
|
||||
* Documentation Page Collection Template
|
||||
*
|
||||
* A complete documentation page schema with:
|
||||
* - Title, description
|
||||
* - Sidebar ordering
|
||||
* - Nested folder support
|
||||
* - Rich-text content with code blocks
|
||||
*
|
||||
* Usage:
|
||||
* import { docPageCollection } from './collections/doc-page'
|
||||
*
|
||||
* export default defineConfig({
|
||||
* schema: {
|
||||
* collections: [docPageCollection]
|
||||
* }
|
||||
* })
|
||||
*
|
||||
* File structure supports nested docs:
|
||||
* content/docs/
|
||||
* ├── getting-started.mdx
|
||||
* ├── installation/
|
||||
* │ ├── nextjs.mdx
|
||||
* │ └── vite.mdx
|
||||
* └── api/
|
||||
* ├── authentication.mdx
|
||||
* └── endpoints.mdx
|
||||
*/
|
||||
export const docPageCollection: Collection = {
|
||||
name: 'doc',
|
||||
label: 'Documentation',
|
||||
path: 'content/docs',
|
||||
format: 'mdx',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'title',
|
||||
label: 'Title',
|
||||
isTitle: true,
|
||||
required: true,
|
||||
description: 'Page title shown in sidebar and at top of page',
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'description',
|
||||
label: 'Description',
|
||||
ui: {
|
||||
component: 'textarea',
|
||||
},
|
||||
description: 'Short description shown below title and in search results',
|
||||
},
|
||||
{
|
||||
type: 'number',
|
||||
name: 'order',
|
||||
label: 'Order',
|
||||
description: 'Sort order in sidebar (lower numbers appear first)',
|
||||
ui: {
|
||||
parse: (val) => Number(val),
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'category',
|
||||
label: 'Category',
|
||||
description: 'Category for grouping related documentation',
|
||||
options: [
|
||||
{ label: 'Getting Started', value: 'getting-started' },
|
||||
{ label: 'Guides', value: 'guides' },
|
||||
{ label: 'API Reference', value: 'api' },
|
||||
{ label: 'Tutorials', value: 'tutorials' },
|
||||
{ label: 'Examples', value: 'examples' },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'datetime',
|
||||
name: 'lastUpdated',
|
||||
label: 'Last Updated',
|
||||
description: 'Automatically updated when page is saved',
|
||||
ui: {
|
||||
dateFormat: 'YYYY-MM-DD',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'object',
|
||||
name: 'sidebar',
|
||||
label: 'Sidebar Configuration',
|
||||
fields: [
|
||||
{
|
||||
type: 'boolean',
|
||||
name: 'hide',
|
||||
label: 'Hide from Sidebar',
|
||||
description: 'If checked, page won\'t appear in sidebar navigation',
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'label',
|
||||
label: 'Custom Sidebar Label',
|
||||
description: 'Override the title shown in sidebar (leave blank to use page title)',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'object',
|
||||
name: 'navigation',
|
||||
label: 'Page Navigation',
|
||||
description: 'Configure prev/next links at bottom of page',
|
||||
fields: [
|
||||
{
|
||||
type: 'reference',
|
||||
name: 'prev',
|
||||
label: 'Previous Page',
|
||||
collections: ['doc'],
|
||||
},
|
||||
{
|
||||
type: 'reference',
|
||||
name: 'next',
|
||||
label: 'Next Page',
|
||||
collections: ['doc'],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'rich-text',
|
||||
name: 'body',
|
||||
label: 'Body',
|
||||
isBody: true,
|
||||
templates: [
|
||||
// Add custom MDX components here if needed
|
||||
// Example:
|
||||
// {
|
||||
// name: 'Callout',
|
||||
// label: 'Callout',
|
||||
// fields: [
|
||||
// {
|
||||
// name: 'type',
|
||||
// label: 'Type',
|
||||
// type: 'string',
|
||||
// options: ['info', 'warning', 'error', 'success'],
|
||||
// },
|
||||
// {
|
||||
// name: 'children',
|
||||
// label: 'Content',
|
||||
// type: 'rich-text',
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
],
|
||||
},
|
||||
],
|
||||
ui: {
|
||||
router: ({ document }) => {
|
||||
// Support nested docs: /docs/installation/nextjs
|
||||
const breadcrumbs = document._sys.breadcrumbs.join('/')
|
||||
return `/docs/${breadcrumbs}`
|
||||
},
|
||||
defaultItem: () => ({
|
||||
title: 'New Documentation Page',
|
||||
category: 'guides',
|
||||
lastUpdated: new Date().toISOString(),
|
||||
sidebar: {
|
||||
hide: false,
|
||||
},
|
||||
}),
|
||||
},
|
||||
}
|
||||
262
templates/collections/landing-page.ts
Normal file
262
templates/collections/landing-page.ts
Normal file
@@ -0,0 +1,262 @@
|
||||
import type { Collection } from 'tinacms'
|
||||
|
||||
/**
|
||||
* Landing Page Collection Template
|
||||
*
|
||||
* A complete landing page schema with multiple templates:
|
||||
* - Basic page (simple content)
|
||||
* - Marketing page (hero, features, CTA)
|
||||
*
|
||||
* Usage:
|
||||
* import { landingPageCollection } from './collections/landing-page'
|
||||
*
|
||||
* export default defineConfig({
|
||||
* schema: {
|
||||
* collections: [landingPageCollection]
|
||||
* }
|
||||
* })
|
||||
*
|
||||
* When using templates, documents must include _template field:
|
||||
* ---
|
||||
* _template: marketing
|
||||
* title: Homepage
|
||||
* ---
|
||||
*/
|
||||
export const landingPageCollection: Collection = {
|
||||
name: 'page',
|
||||
label: 'Landing Pages',
|
||||
path: 'content/pages',
|
||||
format: 'mdx',
|
||||
templates: [
|
||||
{
|
||||
name: 'basic',
|
||||
label: 'Basic Page',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'title',
|
||||
label: 'Title',
|
||||
isTitle: true,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'description',
|
||||
label: 'Description',
|
||||
ui: {
|
||||
component: 'textarea',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'rich-text',
|
||||
name: 'body',
|
||||
label: 'Body',
|
||||
isBody: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'marketing',
|
||||
label: 'Marketing Page',
|
||||
ui: {
|
||||
defaultItem: {
|
||||
hero: {
|
||||
buttonText: 'Get Started',
|
||||
},
|
||||
features: [],
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'title',
|
||||
label: 'Title',
|
||||
isTitle: true,
|
||||
required: true,
|
||||
description: 'Page title (used in browser tab and SEO)',
|
||||
},
|
||||
{
|
||||
type: 'object',
|
||||
name: 'hero',
|
||||
label: 'Hero Section',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'headline',
|
||||
label: 'Headline',
|
||||
required: true,
|
||||
description: 'Main headline (45-65 characters works best)',
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'subheadline',
|
||||
label: 'Subheadline',
|
||||
ui: {
|
||||
component: 'textarea',
|
||||
},
|
||||
description: 'Supporting text below headline (150-200 characters)',
|
||||
},
|
||||
{
|
||||
type: 'image',
|
||||
name: 'image',
|
||||
label: 'Hero Image',
|
||||
description: 'Main hero image (1920x1080px recommended)',
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'buttonText',
|
||||
label: 'Button Text',
|
||||
description: 'Call-to-action button text',
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'buttonUrl',
|
||||
label: 'Button URL',
|
||||
description: 'Where the CTA button links to',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'object',
|
||||
name: 'features',
|
||||
label: 'Features Section',
|
||||
list: true,
|
||||
ui: {
|
||||
itemProps: (item) => ({
|
||||
label: item?.title || 'New Feature',
|
||||
}),
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
type: 'image',
|
||||
name: 'icon',
|
||||
label: 'Icon',
|
||||
description: 'Feature icon (SVG or PNG, 64x64px)',
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'title',
|
||||
label: 'Title',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'description',
|
||||
label: 'Description',
|
||||
ui: {
|
||||
component: 'textarea',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'object',
|
||||
name: 'testimonials',
|
||||
label: 'Testimonials',
|
||||
list: true,
|
||||
ui: {
|
||||
itemProps: (item) => ({
|
||||
label: item?.author || 'New Testimonial',
|
||||
}),
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'quote',
|
||||
label: 'Quote',
|
||||
required: true,
|
||||
ui: {
|
||||
component: 'textarea',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'author',
|
||||
label: 'Author',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'role',
|
||||
label: 'Role',
|
||||
description: 'Job title and company',
|
||||
},
|
||||
{
|
||||
type: 'image',
|
||||
name: 'avatar',
|
||||
label: 'Avatar',
|
||||
description: 'Author photo (square, 200x200px)',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'object',
|
||||
name: 'cta',
|
||||
label: 'Call to Action',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'headline',
|
||||
label: 'Headline',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'text',
|
||||
label: 'Supporting Text',
|
||||
ui: {
|
||||
component: 'textarea',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'buttonText',
|
||||
label: 'Button Text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'buttonUrl',
|
||||
label: 'Button URL',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'object',
|
||||
name: 'seo',
|
||||
label: 'SEO Metadata',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'metaTitle',
|
||||
label: 'Meta Title',
|
||||
description: 'SEO title (leave blank to use page title)',
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'metaDescription',
|
||||
label: 'Meta Description',
|
||||
ui: {
|
||||
component: 'textarea',
|
||||
},
|
||||
description: 'SEO description (150-160 characters)',
|
||||
},
|
||||
{
|
||||
type: 'image',
|
||||
name: 'ogImage',
|
||||
label: 'Open Graph Image',
|
||||
description: 'Image for social media sharing (1200x630px)',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
ui: {
|
||||
router: ({ document }) => {
|
||||
// Root pages like /about, /pricing
|
||||
return `/${document._sys.filename}`
|
||||
},
|
||||
},
|
||||
}
|
||||
19
templates/nextjs/.env.example
Normal file
19
templates/nextjs/.env.example
Normal file
@@ -0,0 +1,19 @@
|
||||
# TinaCloud Credentials
|
||||
# Get these from https://app.tina.io after creating a project
|
||||
|
||||
# Client ID (public, safe to expose)
|
||||
NEXT_PUBLIC_TINA_CLIENT_ID=your_client_id_here
|
||||
|
||||
# Read-only token (keep secret, never commit)
|
||||
TINA_TOKEN=your_read_only_token_here
|
||||
|
||||
# Git branch (auto-detected on Vercel/Netlify, set manually for local dev)
|
||||
# GITHUB_BRANCH=main
|
||||
|
||||
# For self-hosted backend with Auth.js
|
||||
# NEXTAUTH_SECRET=generate_a_secret_key_here
|
||||
# TINA_PUBLIC_IS_LOCAL=false
|
||||
|
||||
# For self-hosted with OAuth providers (example: Discord)
|
||||
# DISCORD_CLIENT_ID=your_discord_client_id
|
||||
# DISCORD_CLIENT_SECRET=your_discord_client_secret
|
||||
24
templates/nextjs/package.json
Normal file
24
templates/nextjs/package.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "nextjs-tinacms-app",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "tinacms dev -c \"next dev\"",
|
||||
"build": "tinacms build && next build",
|
||||
"start": "tinacms build && next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "^16.0.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"tinacms": "^2.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tinacms/cli": "^1.11.0",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
88
templates/nextjs/tina-config-app-router.ts
Normal file
88
templates/nextjs/tina-config-app-router.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { defineConfig } from 'tinacms'
|
||||
import { blogPostCollection } from './collections/blog-post'
|
||||
import { docPageCollection } from './collections/doc-page'
|
||||
import { authorCollection } from './collections/author'
|
||||
|
||||
/**
|
||||
* TinaCMS Configuration for Next.js App Router
|
||||
*
|
||||
* This config file:
|
||||
* - Sets up TinaCloud connection (or self-hosted)
|
||||
* - Defines content collections and their schemas
|
||||
* - Configures media handling
|
||||
* - Sets up build output
|
||||
*
|
||||
* Usage:
|
||||
* 1. Copy this file to: tina/config.ts
|
||||
* 2. Copy collection files to: tina/collections/
|
||||
* 3. Set environment variables in .env.local
|
||||
* 4. Run: npm run dev
|
||||
* 5. Access admin: http://localhost:3000/admin/index.html
|
||||
*/
|
||||
|
||||
// Get Git branch from environment
|
||||
const branch =
|
||||
process.env.GITHUB_BRANCH ||
|
||||
process.env.VERCEL_GIT_COMMIT_REF ||
|
||||
process.env.HEAD ||
|
||||
'main'
|
||||
|
||||
export default defineConfig({
|
||||
// Git branch to use
|
||||
branch,
|
||||
|
||||
// TinaCloud credentials (get from https://app.tina.io)
|
||||
clientId: process.env.NEXT_PUBLIC_TINA_CLIENT_ID,
|
||||
token: process.env.TINA_TOKEN,
|
||||
|
||||
// Build configuration
|
||||
build: {
|
||||
outputFolder: 'admin', // Where admin UI is built
|
||||
publicFolder: 'public', // Your public assets folder
|
||||
},
|
||||
|
||||
// Media configuration
|
||||
media: {
|
||||
tina: {
|
||||
mediaRoot: 'uploads', // Subfolder for uploads
|
||||
publicFolder: 'public', // Where files are stored
|
||||
},
|
||||
},
|
||||
|
||||
// Content schema
|
||||
schema: {
|
||||
collections: [
|
||||
// Import your collections here
|
||||
blogPostCollection,
|
||||
authorCollection,
|
||||
docPageCollection,
|
||||
|
||||
// Or define collections inline:
|
||||
// {
|
||||
// name: 'post',
|
||||
// label: 'Blog Posts',
|
||||
// path: 'content/posts',
|
||||
// format: 'mdx',
|
||||
// fields: [
|
||||
// {
|
||||
// type: 'string',
|
||||
// name: 'title',
|
||||
// label: 'Title',
|
||||
// isTitle: true,
|
||||
// required: true,
|
||||
// },
|
||||
// {
|
||||
// type: 'rich-text',
|
||||
// name: 'body',
|
||||
// label: 'Body',
|
||||
// isBody: true,
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
],
|
||||
},
|
||||
|
||||
// Optional: Self-hosted backend configuration
|
||||
// Uncomment if using self-hosted backend
|
||||
// contentApiUrlOverride: '/api/tina/gql',
|
||||
})
|
||||
64
templates/nextjs/tina-config-pages-router.ts
Normal file
64
templates/nextjs/tina-config-pages-router.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { defineConfig } from 'tinacms'
|
||||
import { blogPostCollection } from './collections/blog-post'
|
||||
import { docPageCollection } from './collections/doc-page'
|
||||
import { authorCollection } from './collections/author'
|
||||
|
||||
/**
|
||||
* TinaCMS Configuration for Next.js Pages Router
|
||||
*
|
||||
* This config file works with Next.js 12 and Pages Router setup.
|
||||
*
|
||||
* Differences from App Router:
|
||||
* - Admin route: pages/admin/[[...index]].tsx (not app/)
|
||||
* - Data fetching: getStaticProps + getStaticPaths
|
||||
* - Client hook: useTina for visual editing
|
||||
*
|
||||
* Usage:
|
||||
* 1. Copy this file to: tina/config.ts
|
||||
* 2. Copy collection files to: tina/collections/
|
||||
* 3. Set environment variables in .env.local
|
||||
* 4. Run: npm run dev
|
||||
* 5. Access admin: http://localhost:3000/admin/index.html
|
||||
*/
|
||||
|
||||
// Get Git branch from environment
|
||||
const branch =
|
||||
process.env.GITHUB_BRANCH ||
|
||||
process.env.VERCEL_GIT_COMMIT_REF ||
|
||||
process.env.HEAD ||
|
||||
'main'
|
||||
|
||||
export default defineConfig({
|
||||
// Git branch to use
|
||||
branch,
|
||||
|
||||
// TinaCloud credentials (get from https://app.tina.io)
|
||||
clientId: process.env.NEXT_PUBLIC_TINA_CLIENT_ID,
|
||||
token: process.env.TINA_TOKEN,
|
||||
|
||||
// Build configuration
|
||||
build: {
|
||||
outputFolder: 'admin',
|
||||
publicFolder: 'public',
|
||||
},
|
||||
|
||||
// Media configuration
|
||||
media: {
|
||||
tina: {
|
||||
mediaRoot: 'uploads',
|
||||
publicFolder: 'public',
|
||||
},
|
||||
},
|
||||
|
||||
// Content schema
|
||||
schema: {
|
||||
collections: [
|
||||
blogPostCollection,
|
||||
authorCollection,
|
||||
docPageCollection,
|
||||
],
|
||||
},
|
||||
|
||||
// Optional: Self-hosted backend configuration
|
||||
// contentApiUrlOverride: '/api/tina/gql',
|
||||
})
|
||||
18
templates/vite-react/.env.example
Normal file
18
templates/vite-react/.env.example
Normal file
@@ -0,0 +1,18 @@
|
||||
# TinaCloud Credentials
|
||||
# Get these from https://app.tina.io after creating a project
|
||||
|
||||
# Note: Vite requires VITE_ prefix for environment variables
|
||||
# These variables are exposed to the client, so only use public values
|
||||
|
||||
# Client ID (public, safe to expose)
|
||||
VITE_TINA_CLIENT_ID=your_client_id_here
|
||||
|
||||
# Read-only token (keep secret, but exposed to client in Vite)
|
||||
# For production, use backend proxy to keep this secret
|
||||
VITE_TINA_TOKEN=your_read_only_token_here
|
||||
|
||||
# Git branch
|
||||
VITE_GITHUB_BRANCH=main
|
||||
|
||||
# For self-hosted backend
|
||||
# VITE_TINA_API_URL=/api/tina/gql
|
||||
25
templates/vite-react/package.json
Normal file
25
templates/vite-react/package.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "vite-react-tinacms-app",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "tinacms dev -c \"vite\"",
|
||||
"build": "tinacms build && vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-router-dom": "^6.28.0",
|
||||
"tinacms": "^2.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tinacms/cli": "^1.11.0",
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"@vitejs/plugin-react": "^4.3.0",
|
||||
"typescript": "^5.6.0",
|
||||
"vite": "^7.1.0"
|
||||
}
|
||||
}
|
||||
69
templates/vite-react/tina-config.ts
Normal file
69
templates/vite-react/tina-config.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { defineConfig } from 'tinacms'
|
||||
import { blogPostCollection } from './collections/blog-post'
|
||||
import { docPageCollection } from './collections/doc-page'
|
||||
import { authorCollection } from './collections/author'
|
||||
|
||||
/**
|
||||
* TinaCMS Configuration for Vite + React
|
||||
*
|
||||
* This config works with Vite + React applications.
|
||||
*
|
||||
* Key Differences from Next.js:
|
||||
* - Environment variables use VITE_ prefix
|
||||
* - Admin interface setup is more manual
|
||||
* - Data fetching requires custom implementation
|
||||
*
|
||||
* Usage:
|
||||
* 1. Copy this file to: tina/config.ts
|
||||
* 2. Copy collection files to: tina/collections/
|
||||
* 3. Set environment variables in .env
|
||||
* 4. Run: npm run dev
|
||||
* 5. Access admin: http://localhost:3000/admin/index.html
|
||||
*
|
||||
* Visual Editing:
|
||||
* - Import useTina from 'tinacms/dist/react'
|
||||
* - Wrap your components with useTina hook
|
||||
* - See templates for examples
|
||||
*/
|
||||
|
||||
// Get Git branch from environment
|
||||
const branch =
|
||||
process.env.VITE_GITHUB_BRANCH ||
|
||||
process.env.VITE_VERCEL_GIT_COMMIT_REF ||
|
||||
'main'
|
||||
|
||||
export default defineConfig({
|
||||
// Git branch to use
|
||||
branch,
|
||||
|
||||
// TinaCloud credentials
|
||||
// Note: Use VITE_ prefix for Vite environment variables
|
||||
clientId: process.env.VITE_TINA_CLIENT_ID,
|
||||
token: process.env.VITE_TINA_TOKEN,
|
||||
|
||||
// Build configuration
|
||||
build: {
|
||||
outputFolder: 'admin',
|
||||
publicFolder: 'public',
|
||||
},
|
||||
|
||||
// Media configuration
|
||||
media: {
|
||||
tina: {
|
||||
mediaRoot: 'uploads',
|
||||
publicFolder: 'public',
|
||||
},
|
||||
},
|
||||
|
||||
// Content schema
|
||||
schema: {
|
||||
collections: [
|
||||
blogPostCollection,
|
||||
authorCollection,
|
||||
docPageCollection,
|
||||
],
|
||||
},
|
||||
|
||||
// Optional: Self-hosted backend
|
||||
// contentApiUrlOverride: '/api/tina/gql',
|
||||
})
|
||||
21
templates/vite-react/vite.config.ts
Normal file
21
templates/vite-react/vite.config.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
/**
|
||||
* Vite Configuration for TinaCMS + React
|
||||
*
|
||||
* Key settings:
|
||||
* - React plugin for JSX support
|
||||
* - Port 3000 (TinaCMS default)
|
||||
* - Host 0.0.0.0 for Docker compatibility
|
||||
*/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: {
|
||||
port: 3000,
|
||||
host: '0.0.0.0', // Allows external connections (Docker, network)
|
||||
},
|
||||
build: {
|
||||
outDir: 'dist',
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user