.cursor/skills/blog-mdx-content/SKILL.md
Create and manage blog posts using MDX with embedded Astro components. Use when writing blog posts, using components in MDX content, configuring CloudCannon snippets, or understanding blog pagination.
npx skillsauth add CloudCannon/astro-component-starter blog-mdx-contentInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Blog posts are MDX files in src/content/blog/. They support standard Markdown plus embedded Astro components for rich, interactive content sections within articles.
Posts live at src/content/blog/{filename}.mdx. The filename becomes the URL slug:
| File | URL |
| ----------------------------------------------------- | --------------------------------------- |
| src/content/blog/my-first-post.mdx | /blog/my-first-post/ |
| src/content/blog/2025-10-15-launch-announcement.mdx | /blog/2025-10-15-launch-announcement/ |
---
_schema: default
title: Blog Post Title
description: A brief description of the post.
date: 2025-10-15T00:00:00Z
author: Author Name
image: /src/assets/images/blog/post-image.jpg
tags:
- Design
- Development
---
| Field | Type | Required | Default |
| ------------- | -------- | -------- | ------------- |
| title | string | Yes | — |
| description | string | Yes | — |
| date | date | Yes | — |
| author | string | No | "Anonymous" |
| image | string | No | — |
| tags | string[] | No | [] |
The _schema: default line tells CloudCannon to use the blog post schema (.cloudcannon/schemas/blog-post.mdx).
The content collection schema in src/content.config.ts validates these fields:
const blogPostSchema = z.object({
title: z.string(),
description: z.string(),
date: z.coerce.date(),
author: z.string().default('Anonymous'),
image: z.string().optional(),
tags: z.array(z.string()).default([]),
});
After the frontmatter, write standard Markdown. MDX extends Markdown with JSX component syntax.
---
title: My Post
description: A great post.
date: 2025-10-15
---
This is a paragraph with **bold** and _italic_ text.
## A Heading
Another paragraph with a [link](/page/).
- List item one
- List item two
> A blockquote
All building block and page section components are automatically available in blog posts by their PascalCase filename. No imports needed.
Here is an inline image:
<Image
source="/src/assets/images/photo.jpg"
rounded={true}
aspectRatio="landscape"
alt="Photo description"
/>
And a full-width CTA section:
<CtaCenter
buttonSections={[
{
_component: 'building-blocks/core-elements/button',
text: 'Get Started',
variant: 'primary',
size: 'md',
link: '/',
},
]}
colorScheme="dark"
backgroundColor="base"
heading="Ready to start?"
subtext="Take the next step today."
rounded={true}
class="wide"
style="margin-top: var(--spacing-xl);"
/>
The blog renderer (src/pages/blog/[...slug].astro) uses import.meta.glob to load all components from building-blocks and page-sections. The component name in MDX is the PascalCase filename without the .astro extension:
| File | MDX component name |
| -------------------------- | ------------------------ |
| Image.astro | <Image /> |
| CtaCenter.astro | <CtaCenter /> |
| TestimonialSection.astro | <TestimonialSection /> |
| FeatureGrid.astro | <FeatureGrid /> |
| CustomSection.astro | <CustomSection /> |
Blog content uses a CSS grid layout with a centered content column (70ch max). To make an element span the full width, add the wide class:
<Image source="/src/assets/images/banner.jpg" alt="Banner" class="wide" />
<CtaCenter heading="Full width CTA" class="wide" />
Elements that automatically get full width: <pre> (code blocks), .image, .video.
prop="value" or prop={'value'}prop={true} or just prop for trueprop={42}prop={[{ key: "value" }]} (JSX expression syntax)style="margin-top: var(--spacing-xl);"<Image
source="/src/assets/images/blog/photo.jpg"
alt="Description"
rounded={true}
aspectRatio="landscape"
/>
<TestimonialSection
text="A great testimonial quote."
authorName="Jane Doe"
authorDescription="CEO, Company"
authorImage="/src/assets/images/team/jane.jpg"
paddingVertical="sm"
class="wide"
/>
<CtaCenter
heading="Call to action"
subtext="Supporting text."
buttonSections={[
{
_component: 'building-blocks/core-elements/button',
text: 'Learn More',
variant: 'primary',
size: 'md',
link: '/page/',
},
]}
colorScheme="dark"
backgroundColor="base"
rounded={true}
class="wide"
style="margin-top: var(--spacing-xl);"
/>
<FeatureGrid
heading="Key points"
features={[
{ title: 'Point 1', description: 'Details.', iconName: 'bolt', iconColor: 'blue' },
{ title: 'Point 2', description: 'Details.', iconName: 'shield-check', iconColor: 'green' },
]}
colorScheme="inherit"
backgroundColor="surface"
class="wide"
/>
Snippets allow CloudCannon editors to insert components into MDX content through a visual picker, without writing JSX.
*.cloudcannon.snippets.yml files next to their componentscloudcannon.config.yml loads them via glob: _snippets_from_glob: - /**/*.cloudcannon.snippets.yml_snippets_imports: { mdx: true }Each snippet file defines how a component maps between the CloudCannon editor and MDX output:
ctaCenter:
template: mdx_component
inline: false
preview:
text:
- CTA Center
subtext:
- key: heading
icon: hero
definitions:
component_name: CtaCenter
named_args:
- editor_key: heading
type: string
optional: true
remove_empty: true
- editor_key: subtext
type: string
optional: true
remove_empty: true
- editor_key: buttonSections
type: array
optional: true
- editor_key: colorScheme
type: string
optional: true
remove_empty: true
- editor_key: backgroundColor
type: string
optional: true
remove_empty: true
- editor_key: rounded
type: boolean
optional: true
_inputs_from_glob:
- /src/components/page-sections/ctas/cta-center/cta-center.cloudcannon.inputs.yml
| Field | Purpose |
| ---------------------------- | ---------------------------------------------------- |
| template | Always mdx_component for MDX components |
| inline | false for block-level components |
| preview | How the snippet appears in the editor |
| definitions.component_name | PascalCase component name for MDX output |
| definitions.named_args | Maps editor fields to component props |
| _inputs_from_glob | Reuses the component's CloudCannon input definitions |
| type | MDX output | Notes |
| --------- | -------------- | ---------------------------------------------- |
| string | prop="value" | Use remove_empty: true to omit empty strings |
| boolean | prop={true} | Omitted when false |
| array | prop={[...]} | Do not use remove_empty |
| number | prop={42} | — |
{slug}.cloudcannon.snippets.yml next to the componenttemplate: mdx_component and inline: falsecomponent_name to the PascalCase filenamenamed_args entry with the correct type_inputs_from_globcloudcannon.config.ymlThe blog index is rendered by src/pages/blog/[...page].astro. It:
blog collectionsrc/content/pages/blog.md to display above the post gridEdit src/content/pages/blog.md to configure the hero section shown above the post grid:
---
_schema: default
title: Blog
description: Read our latest articles.
pageSections:
- _component: page-sections/heroes/hero-center
heading: All posts
subtext: >-
Description text for the blog index.
buttonSections: []
colorScheme: inherit
backgroundColor: base
---
Each post card shows:
image is set)The Pagination component renders page navigation below the post grid. URLs follow the pattern /blog/, /blog/2/, /blog/3/, etc.
The blog collection is configured in cloudcannon.config.yml:
blog:
path: src/content/blog
glob:
- '**/*.mdx'
url: /blog/[full_slug]/
icon: article
_enabled_editors:
- content
- visual
schemas:
default:
name: New Blog Post
path: .cloudcannon/schemas/blog-post.mdx
create:
path: '[relative_base_path]/{filename|slugify|lowercase}.mdx'
new_preview_url: /blog/
Editors can create new posts from CloudCannon using the "Add New Blog Post" button, which scaffolds from the schema template.
When migrating blog posts from another platform:
.mdx file with proper frontmatter<img> tags → <Image source="..." alt="..." /><blockquote> with author → <TestimonialSection ... /><CtaCenter ... /><Video videoId="..." provider="youtube" />src/assets/images/blog/ and update pathsYYYY-MM-DD)tags arraytesting
Customize the design system for brand matching. Use when changing colors, fonts, spacing, or other design tokens, extending the theme system, or migrating an existing brand into this Astro component starter.
data-ai
Configure site-wide data including navigation, footer, and SEO. Use when setting up or editing mainNav.json, footer.json, seo.json, or understanding how navigation components consume data.
development
Build a new page section component from a screenshot. Use when the user pastes a screenshot of a UI section and wants it turned into an Astro component with CloudCannon configuration.
development
Assemble pages from existing components in the Astro + CloudCannon component library. Use when building new pages, populating pageSections YAML, choosing which components to use, or understanding how page content files work.