The Error That Haunted Me
The collection "blog" does not exist or is empty.
Please check your content config file for errors.
Simple enough, right? Except the collection did exist. I could see it. Markdown files in my content collection directory, all with proper frontmatter, all marked draft: false. The dev server said [content] Synced content yet somehow still threw this cryptic error.
I spent three hours hunting this ghost.
The Setup
I was building a blog system for my Astro site with:
- Markdown files in my content collection directory
- A pagination page (
[...page].astro) to list posts - A detail page (
[slug].astro) for individual posts - A content config defining the schema
Everything looked correct. The TypeScript types were generated. The file structure matched Astro’s conventions. So why was the collection appearing empty at runtime?
The Debugging Rabbit Hole
First Suspect: The Schema
I stared at my content.config.ts for 20 minutes. Had I defined the fields wrong? Were there optional fields missing from the frontmatter?
// src/content.config.ts
import { defineCollection, z } from 'astro:content';
const blog = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
description: z.string(),
pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
author: z.string().default('Anthony Tristan'),
category: z.string().default('General'),
tags: z.array(z.string()).default([]),
heroImage: z.string().optional(),
draft: z.boolean().default(false),
featured: z.boolean().default(false),
}),
});
export const collections = { blog };
Nope. Every field that required data was present. Optional fields had proper defaults.
Second Suspect: File Structure
Maybe Astro wasn’t finding the files? I checked:
- ✅ Files in my collection directory
- ✅
.mdextension - ✅ Proper YAML frontmatter
- ✅ Non-empty body content
All good.
Third Suspect: The Markdown Files
I opened a few markdown files:
---
title: "Article Title"
description: "Article description here..."
pubDate: "2026-03-16"
author: "My Name"
category: "software"
tags: ["Tag1", "Tag2"]
heroImage: "/assets/images/hero.png"
draft: false
---
*Article content here.*
Everything looked fine. All required fields present. Dates in the right format. draft: false.
Wait… was there a placeholder file causing issues?
I had a placeholder markdown file with draft: true in the collection. That wasn’t the problem though — filtering out draft posts should have worked fine.
Fourth Suspect: The Collection Usage
Then I looked at my pagination page more carefully:
// src/pages/blog/[...page].astro
import BaseLayout from '/src/layouts/BlogPost.astro'; // ← 🔴 WRONG!
import BaseLayout from '/src/layouts/BaseLayout.astro'; // ← ✅ CORRECT
export async function getStaticPaths({ paginate }) {
const allPosts = (await getCollection('blog', ({ data }) => !data.draft))
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
const featured = allPosts[0];
const remaining = allPosts.slice(1);
return paginate(remaining, {
props: { featured },
pageSize: 9,
});
}
There it was.
I was importing BlogPost.astro — which is designed for individual blog posts — instead of BaseLayout.astro, which is for pages with multiple posts.
This tiny import mistake was causing the component to fail when it tried to access pagination data that BlogPost.astro wasn’t expecting. Astro couldn’t generate static paths, so it treated the collection as if it were empty.
Why This Was So Hard to Find
- The error message lied. “Collection does not exist” when it actually existed. This was the real villain.
- TypeScript didn’t catch it. Both layouts are valid Astro components, so the type system had no idea one was wrong for this use case.
- Astro’s console output was misleading.
[content] Synced contentmade me think everything was loaded correctly. - The file path was absolute. I had to manually trace through the import path instead of relying on IDE autocomplete to guide me.
The Fix
One line changed:
- import BaseLayout from '/src/layouts/BlogPost.astro';
+ import BaseLayout from '/src/layouts/BaseLayout.astro';
And it worked instantly. All posts appeared. No more error.
How to Prevent This Going Forward
1. Add Content Collection Tests
// tests/content.test.ts
import { getCollection } from 'astro:content';
import { describe, it, expect } from 'vitest';
describe('Blog Collection', () => {
it('should have non-draft posts', async () => {
const posts = await getCollection('blog', ({ data }) => !data.draft);
expect(posts.length).toBeGreaterThan(0);
});
it('should validate all frontmatter fields', async () => {
const posts = await getCollection('blog', ({ data }) => !data.draft);
posts.forEach(post => {
expect(post.data.title).toBeDefined();
expect(post.data.pubDate).toBeDefined();
expect(post.data.description).toBeDefined();
});
});
});
This would catch the “empty collection” error immediately in CI/CD.
2. Enable TypeScript Strict Mode
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"forceConsistentCasingInFileNames": true
}
}
3. Use Pre-commit Hooks
npx husky install
npx husky add .husky/pre-commit "npm run build && npm run test:content"
Validate your content before commits are allowed. Catches issues before they reach main.
4. Document Component Usage
Create a CONTENT_GUIDELINES.md:
# Content Guidelines
## Layouts
- Use `BaseLayout.astro` for **listing/pagination pages**
- Use `BlogPost.astro` for **individual post detail pages**
- Wrong layout = runtime errors with cryptic messages
## Common Mistakes
- ❌ Using BlogPost.astro in pagination pages
- ❌ Leaving draft: true mixed with published content
- ❌ Missing required frontmatter fields
- ✅ Always run tests before pushing
5. Add Better Error Messages
Create a custom error handler in astro.config.mjs:
export default defineConfig({
integrations: [
{
name: 'content-error-handler',
hooks: {
'astro:build:done': async () => {
try {
const posts = await getCollection('blog');
if (posts.length === 0) {
console.warn('⚠️ Blog collection is empty!');
console.warn('Check: src/content.config.ts');
console.warn('Check: src/content/blog/ folder');
}
} catch (e) {
console.error('❌ Content collection error:', e.message);
}
},
},
},
],
});
The Lesson
One wrong import crashed our entire blog.
This taught me that in modern web development:
- Error messages are hints, not facts. Always verify the actual state.
- TypeScript only catches what you tell it to. Strict mode catches more.
- Tests are documentation. A test file showing “collection should have X posts” is clearer than any comment.
- File organization matters. Using the wrong layout from the wrong folder was the root cause.
The fix was one line. The prevention would have been minutes of setup.
What we’re Doing Now
- ✅ Set up Vitest for content collection tests
- ✅ Enabled TypeScript strict mode
- ✅ Installed Husky for pre-commit validation
- ✅ Created a content guidelines document
- ✅ Added custom error logging to astro.config.mjs
The next ghost error that comes around? we’re ready for it.
Have you run into a cryptic error like this? Hit me up on Twitter @atristan_info — we’d love to hear what yours was and how you hunted it down.
The Real Solution: Loader and Schema Fix
After all the above, the true fix was:
1. Add the Loader and Zod Import
In your content.config.ts, make sure to import the required helpers and add the loader property:
import { glob } from 'astro/loaders';
import { z } from 'astro/zod';
const blog = defineCollection({
loader: glob({ base: './src/content/blog', pattern: '**/*.{md,mdx}' }),
schema: z.object({
// ...your schema fields
}),
});
2. Match Schema and Markdown Frontmatter
Ensure that all your markdown files in src/content/blog/ have frontmatter fields that match the schema. For example, if your schema expects a date, use a valid date format (not a string unless coerced):
---
title: our Post
pubDate: 2026-03-17
---
If you use z.coerce.date(), quoted strings are accepted, but be consistent.
3. Restart the Dev Server
After making these changes, restart your Astro dev server to ensure the new config is picked up.
Summary:
- Add the
globloader to your collection definition. - Import
zfromastro/zodfor schema validation. - Make sure your markdown frontmatter matches the schema (especially date fields).
This fixed the collection loading error and got the blog working!
Have you run into a cryptic error like this? Hit me up on Twitter @aquaholicksanon — I’d love to hear what yours was and how you hunted it down.
Comments
Loading comments...
Leave a Comment