withastro/astro · error · Error

[RSS] You can only glob entries within 'src/pages/' when…

Error message

[RSS] You can only glob entries within 'src/pages/' when passing import.meta.glob() directly. Consider mapping the result to an array of RSSFeedItems. See the RSS docs for usage examples: https://docs.astro.build/en/recipes/rss/

What it means

The @astrojs/rss helper pagesGlobToRssItems() converts a raw import.meta.glob() result into RSS items by reading each module's `url` export. Vite only injects a route `url` into modules that live under src/pages/, because only those become routable pages. When a globbed entry's `url` is undefined or null, the helper cannot build a feed link and aborts with this error.

Solutions

  1. Map the glob result to an array of RSSFeedItem objects yourself instead of passing it directly: pull title/description/pubDate from each module's frontmatter and set `link` to the real page URL
  2. If you want automatic URLs, move the globbed files under src/pages/ so each entry is a routable page
  3. Glob the content for metadata only and construct links from your known route pattern (e.g. `/blog/${slug}/`)

Example fix

// before (in src/pages/rss.xml.js)
import rss from '@astrojs/rss';
export const get = () => rss({
  items: import.meta.glob('../content/blog/*.md'), // outside src/pages/ -> throws
});

// after
import rss from '@astrojs/rss';
const posts = Object.entries(import.meta.glob('../content/blog/*.md'));
export const get = async () => rss({
  title: 'My Blog',
  items: await Promise.all(
    posts.map(async ([file, getInfo]) => {
      const { frontmatter } = await getInfo();
      const slug = file.split('/').pop().replace('.md', '');
      return {
        title: frontmatter.title,
        description: frontmatter.description,
        pubDate: frontmatter.pubDate,
        link: `/blog/${slug}/`,
      };
    })
  ),
});
Defensive patterns

Strategy: validation

Validate before calling

// before building the feed, confirm every glob entry yields a route URL
const entries = Object.entries(import.meta.glob('./posts/*.md'));
const withUrls = await Promise.all(
  entries.map(async ([file, getInfo]) => {
    const mod = await getInfo();
    return { file, mod, hasUrl: mod.url != null };
  })
);
if (withUrls.some((e) => !e.hasUrl)) {
  throw new Error(`Glob outside src/pages/ detected for: ${withUrls.filter((e) => !e.hasUrl).map((e) => e.file).join(', ')}`);
}

Type guard

function isRssGlobCandidate(items: Record<string, () => Promise<{ url?: unknown }>>): boolean {
  return Object.keys(items).every((p) => p.startsWith('/src/pages/'));
}

Try / catch

try {
  const feedItems = await pagesGlobToRssItems(import.meta.glob('./*.md'));
} catch (e) {
  if (e instanceof Error && e.message.includes("glob entries within 'src/pages/'")) {
    // fall back to manual mapping from frontmatter + known route pattern
  } else throw e;
}

Prevention

When it happens

Trigger: Passing import.meta.glob() of files outside src/pages/ directly to the RSS `items` option — e.g. globbing src/content/blog/*.md or a root-level posts/ directory from src/pages/rss.xml.js. Also globbing non-route files (data .md/.json that never become pages) so the loaded module has no `url` export.

Common situations: Migrating a blog where content lives in src/content or a top-level posts folder instead of src/pages; following older RSS examples that assumed pages-directory content; switching from explicit item arrays to glob shorthand in an Astro + @astrojs/rss setup.

Related errors


AI-assisted analysis of withastro/astro@52e6c34790 (2026-08-18). Data as JSON: /api/errors/f3efe33926c4926a. Report an issue: GitHub.

Appendix: source

Thrown at packages/astro-rss/src/index.ts:138

						message,
						`The \`items\` property requires at least the \`title\` or \`description\` key. They must be properly typed, as well as \`pubDate\` and \`link\` keys if provided.`,
						`Check your collection's schema, and visit https://docs.astro.build/en/recipes/rss/#generating-items for more info.`,
					].join('\n');
				}

				return message;
			}),
		].join('\n'),
	);
	throw formattedError;
}

export function pagesGlobToRssItems(items: GlobResult): Promise<ValidatedRSSFeedItem[]> {
	return Promise.all(
		Object.entries(items).map(async ([filePath, getInfo]) => {
			const { url, frontmatter } = await getInfo();
			if (url === undefined || url === null) {
				throw new Error(
					`[RSS] You can only glob entries within 'src/pages/' when passing import.meta.glob() directly. Consider mapping the result to an array of RSSFeedItems. See the RSS docs for usage examples: https://docs.astro.build/en/recipes/rss/`,
				);
			}
			const parsedResult = rssSchema
				.refine((val) => val.title || val.description, {
					message: 'At least title or description must be provided.',
					path: ['title', 'description'],
				})
				.safeParse({ ...frontmatter, link: url });

			if (parsedResult.success) {
				return parsedResult.data;
			}
			const formattedError = new Error(
				[
					`[RSS] ${filePath} has invalid or missing frontmatter.\nFix the following properties:`,
					...parsedResult.error.issues.map((zodError) => zodError.message),
				].join('\n'),

View on GitHub (pinned to 52e6c34790)