Back to Blog

Connecting TinaCMS to Astro: Important Nuances

Anastasiia Berest 5 min read

While migrating my portfolio from Hugo to Astro, I ran into some unexpected issues when integrating TinaCMS. It took me a couple of days to sort everything out, so I’m sharing the solution here — hopefully it saves someone some time.

1ļøāƒ£ Installing and initializing TinaCMS

To connect TinaCMS to an Astro project, install the required dependencies and initialize Tina:

npm install tina-cloud-cli tinacms @tinacms/cli

npx tina init

package.json

"scripts": {
  "dev": "tinacms dev -c \"astro dev\"",
  "build": "tinacms build && astro build",
  "preview": "astro preview",
  "astro": "astro"
}

2ļøāƒ£ Checking Tina configuration (tina/config.ts)

Make sure that:

  • the Tina admin app is built into the public folder
  • images uploaded via Tina are stored in public/img
  • /public is never used in URLs

Correct configuration:

build: {
  outputFolder: "admin",
  publicFolder: "public",
},

media: {
  tina: {
    mediaRoot: "img",
    publicFolder: "public",
  },
},

As a result:

  • files physically live in public/img
  • image paths in content use /img/filename.jpg

3ļøāƒ£ Using images in Astro

For images added via TinaCMS, you must not use <Image /> from astro:assets.

Tina stores image paths as strings, while astro:assets expects local assets from src/, which causes build errors.

āŒ Incorrect:

<Image src={heroImage} />

āœ… Correct:

<img src={heroImage} alt="" />

4ļøāƒ£ Checking content.config.ts

In your content schema, the image field must be defined as a string, not as image() from astro:content.

āŒ Incorrect:

heroImage: image(),

āœ… Correct:

heroImage: z.string().optional(),

Example of a correct collection:

const blog = defineCollection({
  loader: glob({ base: "./src/content/blog", pattern: "**/*.{md,mdx}" }),
  schema: () =>
    z.object({
      title: z.string(),
      description: z.string(),
      pubDate: z.coerce.date(),
      heroImage: z.string().optional(),
    }),
});

🧠 Key takeaways

  • public/ is not part of the URL
  • TinaCMS images → public/img
  • In content → only strings (/img/…)
  • In templates → regular <img>
  • astro:assets and TinaCMS are not compatible for images
Anastasiia Berest

Anastasiia Berest

Senior Web UI Engineer

Migrating from Hugo to Astro with TinaCMS? This quick guide covers common pitfalls with image handling, public folders, and schema setup to avoid build errors and save time.