Debugging Content Collection Changes That Don't Show Up in Astro
When a frontmatter change doesn't appear in the UI, the problem could be at any layer: the file, the schema, the disk cache, the server process, or the rendered HTML. Here is how to work through each one.
I added a tag to a post’s frontmatter, restarted the dev server, and the tag did not appear. I restarted again. Nothing. The file was correct. The code that read it looked correct. I spent more time on this than I should have, because I assumed a restart was enough to flush everything. It is not, and the reason is that Astro persists its content collection to disk.
The Problem
When you change frontmatter in an Astro content file and the change does not appear in the running application, the instinct is to restart the dev server. That clears the in-memory state. But Astro also writes a disk cache at .astro/data-store.json, and on startup it reads from that file rather than re-parsing every source file from scratch. If the cache entry for your file was not invalidated, the next start loads the stale data right back into memory.
The issue is not a bug in Astro. The cache is a performance optimisation and it works correctly the vast majority of the time. Astro invalidates entries using a digest of each file’s content. Occasionally, under timing conditions in the file watcher, the digest comparison produces a false result and the cached entry is kept. The result is silent: no warning, no error, just the old value in the UI.
A restart alone does not fix this. The cache file must be deleted first.
Working Through It
The mistake most people make, including me, is jumping straight to restarting the process. When that does not work, they restart again. The correct approach is to treat this as a layered problem and work through each layer in order.
Layer 1: Confirm the file is correct.
Read the frontmatter directly from the file before doing anything else.
head -20 src/content/posts/your-post.mdx
If the value on disk is wrong, nothing downstream will be right. Editors sometimes autosave to a temp location, and it is worth ruling this out before going further.
Layer 2: Check the schema.
Astro validates every content entry against the collection schema defined in content.config.ts using Zod. If the value fails validation, Astro throws an InvalidContentEntryDataError and excludes the entry from the collection entirely. This appears in the terminal, not the browser. A value like "work-in-progress" in a field defined as z.enum(['draft', 'published']) will silently drop the entire post from the collection.
// This rejects anything that is not "draft" or "published"
status: z.enum(['draft', 'published']),
// This accepts any string
tags: z.array(z.string()).default([]),
If you see InvalidContentEntryDataError in the terminal, fix the value to match the schema. The post will not appear until it passes validation.
Layer 3: Inspect the disk cache.
If the file is correct and the schema is not rejecting it, check whether the disk cache has a stale entry. The cache is a large serialised JSON file, but you can find a specific entry with a targeted read:
node -e "
const fs = require('fs');
const raw = fs.readFileSync('.astro/data-store.json', 'utf8');
const idx = raw.indexOf('your-post-slug');
console.log(raw.slice(idx, idx + 400));
"
Find the field you changed in the output and compare it to what is on disk. If they differ, the cache is stale. Delete it:
rm .astro/data-store.json
To reset everything in one go:
rm -rf .astro/
The .astro/ directory is entirely generated output. Astro regenerates it on the next start. Deleting it is safe, and it is listed in .gitignore by default in new Astro projects.
Layer 4: Restart the process after deleting the cache.
Deleting the file while the dev server is running changes nothing for that process. The data is already loaded in memory. Stop the server, delete the cache, then start again. Both steps are required.
Layer 5: Check the rendered HTML.
If the data store now has the correct value and the server has been restarted, but the UI still does not reflect the change, the problem is in the template. The data reached the application correctly, but the template is not rendering it.
Inspect the element in browser devtools. In Astro it is common to pass collection data to client scripts via data-* attributes on rendered elements. Check what those attributes contain:
<!-- The collection data reached the template correctly -->
<a data-tags="js,vuejs" ...>
If the attribute shows the correct value, look at the template logic. A common mistake is a hard limit on how many items are rendered:
<!-- The third tag is silently dropped -->
{data.tags.slice(0, 2).map(t => <span>#{t}</span>)}
This was the final layer in my case. The tag had made it all the way through to the rendered element’s data-tags attribute, but the list layout was slicing the tags array to two items and the third was never rendered. The fix was in the template, not the data.
The Full Chain
| Layer | What to check | How |
|---|---|---|
| File | Frontmatter value on disk | head or editor |
| Schema | Enum constraints and field types | content.config.ts + terminal errors |
| Disk cache | Serialised entry in data store | node one-liner against data-store.json |
| Server process | Stale in-memory data | Stop, delete cache, restart |
| Rendered HTML | Template logic and data-* attributes | Browser devtools |
Work top to bottom. Each layer is independent. A stale cache does not mean the template is correct, and a correct template does not mean the schema is not rejecting the entry further up. If you skip a layer and go straight to restarting, you may fix one problem and miss another entirely.
The next time a frontmatter change does not show up, start at layer one and move down. The answer will be at one of these five points.