Ecopages0.2.0

Content Processor

The @ecopages/content-processor package scans MDX content collections at build time, validates frontmatter with Standard Schema-compatible libraries, and exposes each collection as a typed virtual module: ecopages:content/<collection>.

This docs app uses it for every page under src/content/docs/.

What the processor owns

LayerOwner
File discovery, frontmatter validation, manifest sort@ecopages/content-processor
Generated ecopages:content/* modules (entries, getComponent, …)@ecopages/content-processor
ContentScanner for app build scripts (this site uses it for llms.txt)@ecopages/content-processor
Routes, layouts, sidebar, breadcrumbs, paginationYour app — derive from entries and frontmatter

ContentScanner is the same scan/validate path the processor uses at build time. It does not write llms.txt. This docs app (and the docs-starter template) call it from scripts/generate-llm-docs.ts.

Installation

npm install @ecopages/content-processor

Peer dependency: @ecopages/core.

Configuration

Register the processor in eco.config.ts and wire MDX frontmatter into your JSX or React integration:

import { defineConfig } from '@ecopages/core/config';
import { ecopagesJsxPlugin } from '@ecopages/ecopages-jsx';
import { contentProcessorPlugin } from '@ecopages/content-processor/plugin';
import { withContentMdxPlugins } from '@ecopages/content-processor/mdx';
import remarkGfm from 'remark-gfm';
import { compareDocsEntries, docsFrontmatterSchema } from './src/content/docs';
 
export default defineConfig({
	rootDir: import.meta.dirname,
	integrations: [
		ecopagesJsxPlugin({
			mdx: {
				enabled: true,
				...withContentMdxPlugins({
					remarkPlugins: [remarkGfm],
				}),
			},
		}),
	],
	processors: [
		contentProcessorPlugin({
			options: {
				collections: {
					docs: {
						contentDir: 'content/docs',
						schema: docsFrontmatterSchema,
						orderBy: compareDocsEntries,
						entryType: './src/content/docs#DocsFrontmatter',
					},
				},
			},
		}),
	],
});

React apps use the same helper on reactPlugin({ mdx: { enabled: true, ...withContentMdxPlugins() } }).

Collection keys must be kebab-case. Each key becomes ecopages:content/<key>.

Do not add slug or segments to your frontmatter schema — the processor derives them from the file path.

Dev prewarm

Collections can warm known static content routes during development. Add the public route prefix and choose which manifest entries to render:

docs: {
	contentDir: 'content/docs',
	schema: docsFrontmatterSchema,
	routePrefix: '/docs',
	devPrewarm: 'first',
	devPrewarmReadiness: 'background',
}

devPrewarm accepts 'first', 'all', { limit }, or { slugs }. The default readiness is 'background'; 'beforeReady' waits until the selected paths are rendered before Ecopages reports the framework ready signal (not before the listen port opens). Prewarming stores HTML only for the selected paths on the watch page cache allowlist and does not enable caching for other development routes. Keep the selection small during interactive development: 'all' renders every collection route concurrently and can compete with the page you are navigating to.

Docs app wiring

  • Frontmatter schema and section config — src/content/docs.ts
  • Sidebar navigation — src/lib/content-nav.ts joins section config with processor entries
  • Catch-all route — src/pages/docs/[...slug]/index.tsx resolves entries and renders MDX
  • MDX component dependencies — each document imports the components it renders
  • Layout chrome — src/layouts/docs-layout/

Example frontmatter:

---
title: Introduction
description: Get started with Ecopages.
order: 1
---

Catch-all render pattern:

import { getComponent } from 'ecopages:content/docs/server';
 
const Content = await getComponent('getting-started/introduction');
return await Content({});

Forward the active entry's Dependencies on the catch-all Page. Combine Page-local relative assets with mergePageDependencies():

import { eco, mergePageDependencies } from '@ecopages/core';
import { getComponent, getEntryDependencies } from 'ecopages:content/docs/server';
import { CopyForLlm } from '@/components/copy-for-llm';
 
export default eco.page({
	dependencies: async ({ props }) =>
		mergePageDependencies({ components: [CopyForLlm] }, await getEntryDependencies(props.entry.slug)),
	render: async ({ entry }) => {
		const Content = await getComponent(entry.slug);
		return await Content({});
	},
});

Virtual module API

Each collection exposes ecopages:content/<collection> for metadata and ecopages:content/<collection>/server for MDX components:

import { entries, getEntry, getEntryBySegments } from 'ecopages:content/docs';
import { getComponent, getEntryDependencies } from 'ecopages:content/docs/server';
import type { Entry } from 'ecopages:content/docs';
ExportModuleDescription
entriesentriesReadonly manifest, sorted by orderBy
getEntry(slug)entriesLookup by joined slug, e.g. 'getting-started/introduction'
getEntryBySegments(segments)entriesLookup by segment array
getComponent(slug)serverPromise of the MDX component for the entry
getEntryDependencies(slug)serverReturns { components: [entry] } so collection walks MDX identity. Throws HttpError.NotFound when missing. Combine Page-local relative assets with mergePageDependencies().
EntryentriesType only — frontmatter fields plus slug and segments

Keep Entry on a separate import type line in bundled page files.

Package exports

ImportPurpose
@ecopages/content-processorContentScanner, sort helpers, shared types
@ecopages/content-processor/plugincontentProcessorPlugin() for eco.config.ts
@ecopages/content-processor/typesContentEntry, ContentCollectionModule, EntryComparator
@ecopages/content-processor/mdxremarkFrontmatter, withContentMdxPlugins()

TypeScript

Add one import to the app modules.d.ts:

import '@ecopages/content-processor/types';

The import makes ecopages:content/*, /server, and /browser specifiers resolvable immediately. Run ecopages dev or ecopages build to generate precise types for configured collections.

Related