TINACMS SEO AUDIT · GIT-BACKED CMS · NEXT.JS · MDX FRONTMATTER

Free TinaCMS SEO Audit

TinaCMS stores content as Markdown/MDX files in your git repository — SEO fields are frontmatter fields defined in the schema and edited in the TinaCMS admin sidebar. Meta tags, JSON-LD, sitemaps, and Core Web Vitals are all Next.js frontend responsibilities. DeepSEOAnalysis audits the rendered HTML — validating JSON-LD from TinaCMS frontmatter fields, real CrUX Core Web Vitals, GraphQL API sitemap coverage, AI crawler access for developer content sites, and all five AI visibility signals — with TinaCMS schema field paths and frontend integration locations for every finding.

Audit my TinaCMS site →How we score

WHAT WE CHECK

6 TinaCMS-specific SEO checks

TinaCMS SEO auditing covers frontmatter SEO field coverage and generateMetadata() output, Git-backed content routing and slug structure, JSON-LD from frontmatter fields with property-level validation (including datePublished/Modified implementation and author Reference resolution), GraphQL API sitemap with draft and noindex filtering, real CrUX Core Web Vitals with Next.js image optimisation and ISR, and AI visibility including llms.txt generation from the TinaCMS content API and FAQPage from the faqItems schema field.

🔧

TinaCMS frontmatter SEO fields and generateMetadata() output

TinaCMS stores content as Markdown/MDX files with frontmatter. SEO fields (seoTitle, seoDescription, canonicalUrl, ogImage, noIndex) are defined in the TinaCMS schema and edited in the admin sidebar. The audit validates the rendered frontend meta tag output. The audit checks: meta title presence and quality (the `seoTitle` frontmatter field feeds `generateMetadata()` — if empty, the frontend typically falls back to the `title` field, which is optimised for display rather than search; all posts relying on fallback titles are flagged); meta description coverage (the `seoDescription` or `description` frontmatter field — empty means no meta description in the rendered page; the audit flags all posts missing a meta description); canonical tag accuracy (the `canonicalUrl` frontmatter field provides the canonical — if not set, the canonical should be constructed from the post slug and site origin in `generateMetadata()`; missing canonical tags are flagged); Open Graph tags (the `ogImage` frontmatter stores a relative path to the public directory — the frontend must prepend the site origin to construct an absolute URL; og:image relative URLs fail OG validators and are flagged); noindex consistency (the `noIndex` boolean field must trigger `metadata.robots = "noindex"` in `generateMetadata()` — AND exclude the document from the sitemap; inconsistencies are flagged); and field validation enforcement (TinaCMS schema `validate` functions for SEO fields — max length on seoTitle to prevent oversized meta titles, required validation on critical fields — the audit flags consequences of missing validation).

🔗

TinaCMS Git-backed content routing and slug structure

TinaCMS content documents are stored as Markdown/MDX files; the filename (without extension) typically maps to the URL slug. The audit checks: slug quality (filenames like `my-first-post.mdx` → `/blog/my-first-post` — overly generic, non-descriptive, or keyword-absent filenames produce poor URL slugs; the audit checks slug descriptiveness and length); `generateStaticParams()` coverage (Next.js static generation for TinaCMS requires querying all documents via `client.queries.postConnection()` — the audit checks that all sitemap URLs return 200 responses; 404s indicate gaps in `generateStaticParams()` or documents that exist in TinaCMS but aren\'t handled by the frontend routing); draft vs published status (TinaCMS supports a `published: false` frontmatter field or a status field to mark draft content — the frontend must filter for `published: true` in `generateStaticParams()` to prevent draft content from being served publicly; the audit checks for published:false documents accessible at public URLs); canonical slug consistency (if a post is moved or renamed in the git repository, the old URL returns 404 unless a redirect is configured in `next.config.js`; the audit checks sitemap URLs for 404 responses); and collection URL structure (TinaCMS collections can have different URL prefixes — blog posts at `/blog/[slug]`, pages at `/[slug]`, docs at `/docs/[...slug]` — the audit verifies the URL structure in `generateMetadata()` constructs correct canonical URLs for each collection).

📋

JSON-LD from TinaCMS frontmatter fields

TinaCMS frontmatter fields provide the data for JSON-LD structured data in the Next.js frontend. The audit validates all JSON-LD against Google\'s Rich Results specifications. For `Article` JSON-LD: `headline` from the `seoTitle` or `title` frontmatter field — must be under 110 characters (enforce with a TinaCMS schema `validate` function; the audit flags posts with headlines over 110 characters and recommends adding the validation); `datePublished` from the `publishedAt` frontmatter field (must be added explicitly — it\'s not auto-populated by TinaCMS unless implemented; the audit flags missing `datePublished`); `dateModified` from the `updatedAt` frontmatter field (similar to `datePublished` — requires explicit implementation; a Tina Cloud webhook can write the last publish timestamp to frontmatter automatically; the audit flags missing `dateModified`); `author` from a Reference field pointing to an Authors collection (requires resolving the reference in the GraphQL query — the audit flags null `author` when the author frontmatter field is a filename string rather than a resolved person object with `name`); `image` from the `ogImage` field (relative path requiring origin prepending to create the absolute URL — the audit flags relative `image.url` values in Article JSON-LD). For `FAQPage` JSON-LD: the `faqItems` object array field in the TinaCMS schema provides the question-answer pairs — the audit checks FAQPage JSON-LD for `mainEntity` presence and correct `@type: "Question"` and `acceptedAnswer` structure. Common TinaCMS-specific JSON-LD errors: relative `image.url` from `ogImage` path not prefixed with site origin; null `datePublished` because the `publishedAt` frontmatter field was added after initial posts were created (older documents have no value); null `author` from an unresolved Reference field.

🗺️

TinaCMS GraphQL API sitemap and crawl configuration

TinaCMS has no native sitemap generation — the Next.js frontend queries the TinaCMS GraphQL API to build `app/sitemap.ts`. The audit checks: sitemap accessibility (/sitemap.xml must return valid XML); collection coverage (the sitemap should include all published documents across all TinaCMS collections — blog posts, pages, documentation, case studies — each requiring a separate `client.queries.[collection]Connection()` call; missing collections leave significant content undiscovered by Googlebot without sitemap guidance); `lastmod` accuracy (TinaCMS frontmatter `updatedAt` field maps to sitemap `lastModified` — the audit verifies `lastmod` is present and reflects actual content update dates; if `updatedAt` is not maintained in frontmatter, `lastmod` may be absent or stale); draft exclusion (`{ filter: { published: { eq: true } } }` in the TinaCMS GraphQL query must be applied in the sitemap query — draft documents accessible only in TinaCMS admin should be excluded from the sitemap); noindex exclusion (documents with `noIndex: true` frontmatter must be excluded from the sitemap — the sitemap query must filter for `noIndex: { ne: true }` or the sitemap generation function must filter after retrieval); and robots.txt configuration (`app/robots.ts` should declare `Sitemap:` pointing to the sitemap URL; `/.tina/` and `/admin` paths should be disallowed — the TinaCMS admin interface at `/admin/index.html` is a client-side UI that doesn\'t need to be indexed).

Core Web Vitals for TinaCMS Next.js sites

TinaCMS is a pure content layer — Core Web Vitals are determined by the Next.js frontend and image delivery. The audit uses real CrUX field data. **LCP** — the primary LCP candidate on TinaCMS blog posts is typically the post cover image. TinaCMS `image` fields store a path to a file in the public directory or an external CDN URL. For optimal LCP: use Next.js `Image` with `priority` on the cover image (the LCP element); if images are stored in the git repository in the `public/` folder, Next.js serves them from its built-in image optimizer — configure `images.localPatterns` or `images.remotePatterns` in `next.config.js` for external image CDN URLs; set explicit `width` and `height` props from the image dimensions (TinaCMS doesn\'t store image dimensions by default — add `{ type: "number", name: "imageWidth" }` and `{ type: "number", name: "imageHeight" }` fields or implement a pre-save hook that extracts dimensions); and serve images in WebP or AVIF format via Next.js image optimisation. **INP** — the `useTina()` client hook (used for visual editing) adds client-side JavaScript to the page. In production (non-editing) mode, the visual editing overhead is minimal. React Server Components for article content rendering minimises client-side JavaScript. **CLS** — implement `width` and `height` on images from frontmatter dimensions to prevent CLS. Embedded code blocks, syntax highlighting libraries (Prism, Shiki), and MDX component renders can cause CLS if they load JavaScript asynchronously — use `React.Suspense` with appropriate fallback heights. **TTFB** — TinaCMS Next.js sites using static generation (ISR or full static) have very low TTFB from CDN edge serving. Sites using SSR with `client.queries` in server components without ISR are limited by the TinaCMS GraphQL API response time on each request.

🧠

AI visibility for TinaCMS developer and technical blogs

TinaCMS is used primarily for developer blogs, documentation sites, and technical content — high-value AI citation targets. The audit checks all five AI visibility signals. For `llms.txt`: TinaCMS has no native llms.txt generation. In the Next.js frontend, add `app/llms.txt/route.ts` that generates the llms.txt from a TinaCMS collection query — querying published documents, extracting title and URL, and formatting the llms.txt structure. Alternatively, maintain a `public/llms.txt` static file manually (simpler but requires manual updates as new content is published). For `robots.txt`: in `app/robots.ts`, ensure GPTBot, ClaudeBot, PerplexityBot, Google-Extended, and CCBot are not in any Disallow block. Ensure `/.tina/` is disallowed (TinaCMS admin path) but the public blog content paths are allowed. For `FAQPage` JSON-LD: the `faqItems` frontmatter field group (implemented in the TinaCMS schema as described in the JSON-LD check) provides the data for FAQPage JSON-LD — the audit verifies it\'s present and correctly mapped to FAQPage in the server-rendered HTML. TinaCMS\'s MDX rich text editor allows editors to add any content including FAQ sections, but without the structured `faqItems` field, FAQ content is rendered as Markdown prose without FAQPage JSON-LD. For question-heading ratio: TinaCMS Markdown/MDX content with `# Heading` syntax renders as semantic `<h2>`/`<h3>` elements — the audit checks whether technical post headings include sufficient question-format headings (≥20%) for AI Overview citation eligibility. For content chunkability: MDX content with section headings and focused paragraphs naturally supports good chunkability — the audit verifies average section length (≤400 words) in the rendered HTML.

HOW IT WORKS

Audit your TinaCMS site in 60 seconds

  1. Enter your frontend URL. Use the public URL of your TinaCMS-powered Next.js frontend — not the TinaCMS admin at `/admin/index.html`. No Tina Cloud token, git repository access, or admin credentials needed. Works with any TinaCMS deployment: Vercel, Netlify, self-hosted Node.js, or static export to any CDN.
  2. We crawl and analyse. The engine validates all meta tags generated from TinaCMS frontmatter fields; checks every JSON-LD block (Article, FAQPage) against Google's Rich Results specs with property-level errors including datePublished absence, relative ogImage URLs, and author Reference resolution gaps; verifies the GraphQL API sitemap covers all published documents across all collections with correct draft and noindex filtering; measures CrUX Core Web Vitals from real Chrome field data; and evaluates all five AI visibility signals in the server-rendered HTML.
  3. You get a prioritised report. TinaCMS-specific issues first — missing `publishedAt` frontmatter field causing absent `datePublished` in Article JSON-LD (common when the field is added to the schema after initial content is created), relative `ogImage` paths producing relative `image.url` in JSON-LD, null `author` from an unresolved Reference field, draft content accessible at public URLs due to missing `published` filter in `generateStaticParams()`, `/admin` path missing from `robots.txt` Disallow — then general SEO findings, each with the TinaCMS `.tina/config.ts` schema field path or Next.js integration location for the fix.

FAQ

Questions about the TinaCMS SEO audit

Does TinaCMS have built-in SEO features?

TinaCMS (tina.io) is a Git-backed headless CMS that stores content as Markdown (.md) or MDX (.mdx) files in the repository — SEO fields are defined in the TinaCMS schema and stored as frontmatter in each content file. Key TinaCMS SEO capabilities: (1) **SEO fields in content schema** — developers define the content schema in `.tina/config.ts`, specifying fields for each content type. SEO-relevant fields added to the schema (seoTitle, seoDescription, canonicalUrl, ogImage, noIndex) appear as editor inputs in the TinaCMS admin sidebar and are saved as frontmatter in the Markdown/MDX file. The `useTina()` hook or the TinaCMS GraphQL API makes these frontmatter values available to the Next.js `generateMetadata()` function. (2) **Git-backed content** — because content is stored as Markdown/MDX in the git repository, frontmatter fields (including SEO fields) are version-controlled — every change to a title or meta description is tracked in git history. (3) **TinaCMS GraphQL Content API** — TinaCMS provides a GraphQL API for querying content collections. In production, the API is hosted on Tina Cloud (`content.tinajs.io`). This API is used in `generateStaticParams()` for static generation and in `getStaticProps`/`generateMetadata()` for data fetching. (4) **Local development with local GraphQL** — when running TinaCMS locally (`tinacms dev`), a local GraphQL server runs against the local filesystem, allowing developers to create and edit content via the TinaCMS admin UI (`/admin/index.html`) without needing a cloud connection. (5) **MDX support** — TinaCMS has first-class MDX support; MDX components can be defined in the schema as rich-text blocks with type-safe props. (6) **Visual editing** — TinaCMS supports visual editing in Next.js App Router via the `<TinaProvider>` and `useTina()` hook — editors can click on content in the page preview to open the sidebar editor for that content.

How do I implement generateMetadata() with TinaCMS in Next.js?

In a Next.js App Router application using TinaCMS, `generateMetadata()` reads frontmatter fields from the TinaCMS GraphQL API. The pattern: (1) **Define SEO fields in the schema** — in `.tina/config.ts`, add SEO fields to your content collection: `{ type: "string", name: "seoTitle", label: "SEO Title" }`, `{ type: "string", name: "seoDescription", label: "Meta Description", ui: { component: "textarea" } }`, `{ type: "image", name: "ogImage", label: "OG Image" }`, `{ type: "string", name: "canonicalUrl", label: "Canonical URL" }`, `{ type: "boolean", name: "noIndex", label: "No Index" }`. (2) **Query frontmatter in generateMetadata()** — use the TinaCMS client to fetch the document: `const post = await client.queries.post({ relativePath: slug + ".mdx" })`. Access frontmatter values from `post.data.post`: `post.data.post.seoTitle` or `post.data.post.title` as the fallback, `post.data.post.seoDescription`, `post.data.post.ogImage`, `post.data.post.canonicalUrl`, `post.data.post.noIndex`. (3) **Map to Next.js metadata** — return the metadata object with `title`, `description`, `alternates.canonical` (from `canonicalUrl` field or constructed from the slug), `openGraph.images` (from `ogImage` — must be an absolute URL; TinaCMS image fields store the path in the git repo, which maps to the public URL served by the frontend), and `robots: "noindex"` if `noIndex` is true. (4) **generateStaticParams()** — use `client.queries.postConnection()` to list all documents in the collection for static generation. For each document, extract the `_sys.filename` (the filename without extension, used as the slug) and return it as a param.

How do I add JSON-LD structured data to a TinaCMS Next.js site?

JSON-LD for TinaCMS content is implemented in the Next.js server component, reading from frontmatter fields queried via the TinaCMS GraphQL API. For `Article` JSON-LD: `headline` from `post.data.post.seoTitle` or `post.data.post.title` (keep under 110 characters — add a `validate` function to the TinaCMS field schema to enforce max length in the editor: `ui: { validate: (v) => v?.length > 110 ? "Max 110 characters" : undefined }`); `datePublished` from `post.data.post.publishedAt` (an ISO date string frontmatter field — add a `{ type: "datetime", name: "publishedAt" }` field to the schema); `dateModified` from `post.data.post.updatedAt` (updated when editors save changes; alternatively, derive from git commit date using a Tina Cloud webhook that writes `updatedAt` to frontmatter on each publish); `author` from `post.data.post.author` (a Reference field pointing to an Authors collection — use `client.queries.author({ relativePath: post.data.post.author })` to fetch the author name); `image` from `post.data.post.ogImage` (a TinaCMS `image` type field; the stored value is the path relative to the public directory — prepend the site origin to construct the absolute URL). For `FAQPage` JSON-LD: add a `{ type: "object", name: "faqItems", list: true, fields: [{ type: "string", name: "question" }, { type: "string", name: "answer", ui: { component: "textarea" } }] }` field group to the schema. Editors populate FAQ pairs in the TinaCMS admin; the frontend reads `post.data.post.faqItems` and maps it to the FAQPage `mainEntity` array. Output the JSON-LD via `script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }} /` in a Server Component.

Is the TinaCMS SEO audit free?

Yes. The complete audit is free with no signup and no email gate. Enter the URL of the Next.js frontend consuming your TinaCMS content — the audit crawls the rendered HTML as Googlebot would. No TinaCMS API key, Tina Cloud token, or git repository access needed. Works with any TinaCMS deployment: Vercel, Netlify, self-hosted Node.js, or static export. Paid DeepSEOAnalysis plans add saved report history, scheduled monitoring, larger crawls (up to 1,000 pages), and white-label agency reporting.