This issue was originally identified in voxpelli/voxpelli.github.com#32
Problem
The .vars getter on PageData can throw if the underlying page module has errors (syntax errors, missing dependencies, runtime exceptions during init()). When iterating all pages inside global.data.js, one broken page crashes the entire build with an opaque error that does not identify which page failed.
Expected: The error message tells you which page caused the failure, what went wrong, and where in the resolution chain the error occurred.
Actual: You get a generic "Error resolving page vars" with a serialized cause object whose stack trace has been stripped by the worker thread boundary. In global.data.js, the error surfaces as an unhandled exception with no page path context at all.
Current behavior
The PageData.vars getter in page-data.js performs a shallow merge with no error boundary. Meanwhile, buildPagesDirect() in index.js catches init() errors but wraps them in a way that loses fidelity across the worker boundary:
const variableResolveError = new Error('Error resolving page vars', {
cause: { message: err.message, stack: err.stack }
})
// I can't put stuff on the error, the worker swallows it for some reason.
result.errors.push({ error: variableResolveError, errorData: { page: pageInfo } })
The comment "I can't put stuff on the error, the worker swallows it for some reason" confirms this is a known pain point.
When global.data.js iterates pages, users must defensively wrap every .vars access:
const posts = pages.filter(p => {
try {
return p.vars && p.vars.layout === 'article' && p.vars.date;
} catch {
return false; // Silently swallows which page failed and why
}
})
This defensive pattern exists solely because the framework does not provide useful error messages.
Proposed solution
Option A: Better error wrapping in the getter (recommended)
get vars () {
if (!this.#initialized) throw new Error('Initialize PageData before accessing vars')
try {
const { globalVars, globalDataVars, pageVars, builderVars } = this
return { ...globalVars, ...globalDataVars, ...pageVars, ...builderVars }
} catch (err) {
throw new Error(
`Failed to resolve vars for page "${this.pageInfo.path}": ${err.message}`,
{ cause: err }
)
}
}
Option B: Improve error reporting across the worker boundary
Re-hydrate the serialized cause into a proper Error object with the page path included in the message on the main thread side where errorData.page is already available.
Option C: Validate eagerly during init()
Check that page.vars resolves successfully during PageData.init() and surface errors early before global.data.js is called.
Recommendation: Option A is the highest-value, lowest-risk improvement. Options B and C could follow.
Source code notes
errorData containing { page: pageInfo } is re-assigned to the error object on the main thread via for (const [key, val] of Object.entries(errorData)) { error[key] = val } — so the page path IS available as error.page.path, but the default error formatter does not print it.
Problem
The
.varsgetter onPageDatacan throw if the underlying page module has errors (syntax errors, missing dependencies, runtime exceptions duringinit()). When iterating all pages insideglobal.data.js, one broken page crashes the entire build with an opaque error that does not identify which page failed.Expected: The error message tells you which page caused the failure, what went wrong, and where in the resolution chain the error occurred.
Actual: You get a generic
"Error resolving page vars"with a serializedcauseobject whose stack trace has been stripped by the worker thread boundary. Inglobal.data.js, the error surfaces as an unhandled exception with no page path context at all.Current behavior
The
PageData.varsgetter inpage-data.jsperforms a shallow merge with no error boundary. Meanwhile,buildPagesDirect()inindex.jscatchesinit()errors but wraps them in a way that loses fidelity across the worker boundary:The comment "I can't put stuff on the error, the worker swallows it for some reason" confirms this is a known pain point.
When
global.data.jsiterates pages, users must defensively wrap every.varsaccess:This defensive pattern exists solely because the framework does not provide useful error messages.
Proposed solution
Option A: Better error wrapping in the getter (recommended)
Option B: Improve error reporting across the worker boundary
Re-hydrate the serialized
causeinto a properErrorobject with the page path included in the message on the main thread side whereerrorData.pageis already available.Option C: Validate eagerly during
init()Check that
page.varsresolves successfully duringPageData.init()and surface errors early beforeglobal.data.jsis called.Recommendation: Option A is the highest-value, lowest-risk improvement. Options B and C could follow.
Source code notes
errorDatacontaining{ page: pageInfo }is re-assigned to the error object on the main thread viafor (const [key, val] of Object.entries(errorData)) { error[key] = val }— so the page path IS available aserror.page.path, but the default error formatter does not print it.