Skip to content

fix(content): guard afterParse hook to prevent silent HMR failures#577

Merged
harlan-zw merged 2 commits intomainfrom
worktree-fix-content-afterparse-trycatch
Mar 22, 2026
Merged

fix(content): guard afterParse hook to prevent silent HMR failures#577
harlan-zw merged 2 commits intomainfrom
worktree-fix-content-afterparse-trycatch

Conversation

@harlan-zw
Copy link
Collaborator

@harlan-zw harlan-zw commented Mar 22, 2026

🔗 Linked issue

Related to harlan-zw/nuxt-seo#498

❓ Type of change

  • 📖 Documentation
  • 🐞 Bug fix
  • 👌 Enhancement
  • ✨ New feature
  • 🧹 Chore
  • ⚠️ Breaking change

📚 Description

Nuxt Content's dev file watcher has no error handling around the content:file:afterParse hook. If the hook throws, the broadcast() call never executes and the client never receives the HMR update, so the user sees "file modified" in logs but the page never refreshes.

This wraps the hook body in try/catch (logging instead of throwing) and adds a null guard on ctx.collection.fields before the in operator check.

The primary fix for #498 is #576 (defineSitemapSchema()), which avoids the overlapping collection source issue entirely. This PR is defensive hardening so the hook can never break HMR regardless of the cause.

Nuxt Content's dev file watcher has no error handling around the
content:file:afterParse hook. If the hook throws, the entire HMR
update silently fails (no broadcast, no WebSocket message to client).

Wrap the hook body in try/catch so errors are logged instead of
breaking hot reload. Also add a null guard on ctx.collection.fields
to prevent TypeError when the fields object is unavailable.
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Defensively hardens the Nuxt Content v3 integration so the content:file:afterParse hook cannot break the dev hot-reload pipeline when sitemap metadata processing encounters unexpected input.

Changes:

  • Wraps the content:file:afterParse hook body in try/catch to prevent thrown errors from aborting downstream Nuxt Content HMR handling.
  • Adds a null/undefined guard for ctx.collection.fields before using the in operator.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

src/module.ts Outdated
ctx.content.sitemap = defu(typeof content.sitemap === 'object' ? content.sitemap : {}, defaults) as Partial<SitemapUrl>
}
catch (e) {
logger.warn('Failed to process sitemap data for content file, skipping.', e)
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The catch block logs a generic warning without identifying which content file/collection caused the failure. Including ctx.collection.name and ctx.content.path (or similar identifiers) in the log message/metadata would make these failures actionable when multiple files are being parsed in dev mode.

Suggested change
logger.warn('Failed to process sitemap data for content file, skipping.', e)
logger.warn(`Failed to process sitemap data for content file (collection: ${ctx.collection?.name}, path: ${ctx.content?.path}), skipping.`, e)

Copilot uses AI. Check for mistakes.
Comment on lines 471 to +487
nuxt.hooks.hook('content:file:afterParse' as any, (ctx: FileAfterParseHook) => {
const content = ctx.content as any as {
body: { value: [string, Record<string, any>][] }
sitemap?: Partial<SitemapUrl> | false
path: string
updatedAt?: string
} & Record<string, any>
nuxtV3Collections.add(ctx.collection.name)
// ignore .dot files and paths
if (String(ctx.content.path).includes('/.')) {
ctx.content.sitemap = null
return
}
if (!('sitemap' in ctx.collection.fields)) {
ctx.content.sitemap = null
return
}
// support sitemap: false
if (typeof content.sitemap !== 'undefined' && !content.sitemap) {
ctx.content.sitemap = null
return
}
if (ctx.content.robots === false) {
ctx.content.sitemap = null
return
}
// add any top level images
const images: SitemapUrl['images'] = []
if (config.discoverImages) {
images.push(...(content.body?.value
?.filter(c =>
['image', 'img', 'nuxtimg', 'nuxt-img'].includes(c[0]),
try {
const content = ctx.content as any as {
body: { value: [string, Record<string, any>][] }
sitemap?: Partial<SitemapUrl> | false
path: string
updatedAt?: string
} & Record<string, any>
nuxtV3Collections.add(ctx.collection.name)
// ignore .dot files and paths
if (String(ctx.content.path).includes('/.')) {
ctx.content.sitemap = null
return
}
if (!ctx.collection.fields || !('sitemap' in ctx.collection.fields)) {
ctx.content.sitemap = null
return
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is a regression guard for Nuxt Content v3 parsing/HMR failures, but there’s no automated coverage ensuring the hook stays non-throwing when ctx.collection.fields is missing (or when the hook body throws). Since the repo already has e2e coverage for content-v3, it would be good to add a small fixture/test that exercises a collection with fields undefined and asserts sitemap endpoints still respond (and no unhandled error is thrown).

Copilot uses AI. Check for mistakes.
@harlan-zw harlan-zw merged commit 0ce4e6a into main Mar 22, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants