Skip to content

Improve PageData.vars getter error messages and resilience #233

@bcomnes

Description

@bcomnes

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions