Fix: Invalid Frontmatter Injection in Astro

Error message:
Invalid frontmatter injection.
Markdown & MDX 2025-01-25

What Causes This Error?

This error occurs when a remark or rehype plugin modifies the frontmatter in an invalid way, such as setting non-serializable values or invalid data types that conflict with your schema.

The Problem

// A remark plugin injecting invalid data
function remarkPlugin() {
  return (tree, file) => {
    // ❌ Injecting a function (non-serializable)
    file.data.astro.frontmatter.compute = () => {};

    // ❌ Injecting circular reference
    const obj = {};
    obj.self = obj;
    file.data.astro.frontmatter.data = obj;
  };
}

The Fix

Inject Serializable Data Only

// ✅ Valid frontmatter injection
function remarkPlugin() {
  return (tree, file) => {
    // Strings, numbers, booleans, arrays, plain objects
    file.data.astro.frontmatter.readingTime = '5 min';
    file.data.astro.frontmatter.wordCount = 1234;
    file.data.astro.frontmatter.tags = ['astro', 'markdown'];
  };
}

Common Scenarios

Reading Time Plugin

// remark-reading-time.js
import getReadingTime from 'reading-time';
import { toString } from 'mdast-util-to-string';

export function remarkReadingTime() {
  return function (tree, file) {
    const textOnPage = toString(tree);
    const readingTime = getReadingTime(textOnPage);

    // ✅ Inject serializable values
    file.data.astro.frontmatter.minutesRead = readingTime.text;
  };
}
// astro.config.mjs
import { remarkReadingTime } from './remark-reading-time.js';

export default defineConfig({
  markdown: {
    remarkPlugins: [remarkReadingTime],
  },
});

Update Schema for Injected Fields

// src/content/config.ts
const blog = defineCollection({
  type: 'content',
  schema: z.object({
    title: z.string(),
    // ✅ Add fields that plugins inject
    minutesRead: z.string().optional(),
    wordCount: z.number().optional(),
  }),
});

Auto-Generate Fields

// remark-auto-meta.js
export function remarkAutoMeta() {
  return function (tree, file) {
    const frontmatter = file.data.astro.frontmatter;

    // ✅ Only add if not already set
    if (!frontmatter.lastModified) {
      frontmatter.lastModified = new Date().toISOString();
    }
  };
}

Table of Contents Injection

// remark-toc-data.js
import { toc } from 'mdast-util-toc';

export function remarkTocData() {
  return function (tree, file) {
    const result = toc(tree);

    // ✅ Convert to serializable format
    const headings = extractHeadings(result.map);
    file.data.astro.frontmatter.headings = headings;
  };
}

function extractHeadings(node) {
  // Return plain objects/arrays only
  if (!node) return [];
  // ... extraction logic returning strings/objects
}

Debugging Plugin Output

export function remarkDebug() {
  return function (tree, file) {
    console.log('Current frontmatter:', file.data.astro.frontmatter);

    // Test serialization
    try {
      JSON.stringify(file.data.astro.frontmatter);
      console.log('✅ Frontmatter is serializable');
    } catch (e) {
      console.error('❌ Frontmatter has non-serializable data:', e);
    }
  };
}

Valid Injection Types

// ✅ Valid types for frontmatter
file.data.astro.frontmatter.string = 'text';
file.data.astro.frontmatter.number = 42;
file.data.astro.frontmatter.boolean = true;
file.data.astro.frontmatter.array = ['a', 'b', 'c'];
file.data.astro.frontmatter.object = { key: 'value' };
file.data.astro.frontmatter.null = null;
file.data.astro.frontmatter.date = '2024-01-15'; // String, not Date object

// ❌ Invalid types
file.data.astro.frontmatter.func = () => {};
file.data.astro.frontmatter.symbol = Symbol('x');
file.data.astro.frontmatter.undefined = undefined;
file.data.astro.frontmatter.map = new Map();
file.data.astro.frontmatter.set = new Set();

Third-Party Plugin Issues

// If a third-party plugin causes issues:
// 1. Check plugin documentation
// 2. Update to latest version
// 3. Wrap with sanitization

import problematicPlugin from 'some-plugin';

function safePlugin() {
  const plugin = problematicPlugin();
  return function (tree, file) {
    plugin(tree, file);
    // Sanitize after
    file.data.astro.frontmatter = JSON.parse(
      JSON.stringify(file.data.astro.frontmatter)
    );
  };
}

Quick Checklist

  • Only inject JSON-serializable data
  • No functions, Symbols, Maps, Sets
  • Update schema for injected fields
  • Test with JSON.stringify()
  • Use strings for dates, not Date objects