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
- 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
- If you want automatic URLs, move the globbed files under src/pages/ so each entry is a routable page
- 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
- Only pass import.meta.glob() directly to RSS when the glob target is inside src/pages/
- Keep content used for feeds either in src/pages/ or behind a content collection, and map to RSSFeedItem[] explicitly
- Loudly fail in CI by building the RSS route (astro build) so glob mistakes surface before deploy
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
- ImageNotFound
- UnknownContentCollectionError
- A content collection is defined with legacy features (e.g…
- An error was encountered while creating the JSON schema for…
- Auto-generating collections for folders in "src/content/"…
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)