4

Is it possible to skip creating a page during build if that page ends up throwing during render?

This page is created programmatically via gatsby-node createPage and it's possible that the data from a page query (from our CMS) is bad causing it to throw.

I don't want stop the build due to one bad page so ideally the page wouldn't be created or a fallback page would be put into its place (and an error event would be logged to Sentry or similar).

Any ideas on how to achieve this?


Edit: I didn't clarify my question enough so I wanted to add some context about what problem I'm trying to solve and why.

The error I'm trying to catch occurs during the render of a page during build time. This error occurs because the component I'm trying to render assumed something about the data that isn't true (but should be true).

For example, let's say I'm creating many pages for all the products on my site. The component expects that every product has imagesSizes and calls imagesSizes.split(',') during render. Because imagesSizes is null from the page query, the whole component throws an error and breaks the build.

Like @EliteRaceElephant has suggested, I have tried using React Error Boundaries and unfortunately, they don't work for SSR (which is used by Gatsby during build time). So even if I wrap my component in an error boundary, it still ends up breaking the build.

One a last note, the example I gave above is only one of the situations I run into where the data is bad and breaks the build.

What I'm trying to achieve is a simple fallback page for when any arbitrary error occurs during render during the build. My ideal solution would even allow me to throw errors on purpose when certain assumptions I make about the data aren't true (because I'd rather send the user an error page instead of showing them a page with bad data).


Historically, when I've done SSR outside of Gatsby, I would simply wrap the whole call to ReactDOMServer.renderToString in a try catch block and just return my fallback page in the catch block.

What is the Gatsby equivalent of that?

Rico Kahler
  • 17,616
  • 11
  • 59
  • 85

1 Answers1

3

You can gracefully handle errors because the graphQL query is returned as a promise. Handle the raised error if the promise fails to resolve and keep building your page.

From the Gatsby node API documentation:

const path = require(`path`)
exports.createPages = ({ graphql, actions }) => {
  const { createPage } = actions
  const blogPostTemplate = path.resolve(`src/templates/blog-post.js`)

  return graphql(`
    query loadPagesQuery ($limit: Int!) {
      allMarkdownRemark(limit: $limit) {
        edges {
          node {
            frontmatter {
              slug
            }
          }
        }
      }
    }
  `, { limit: 1000 }).then(result => {
    if (result.errors) {
      throw result.errors

      // ##### Handle your ERROR here #######
      // send to sentry, try another query, etc
      // or pass an empty result object so no pages are created but the build continues

    }
    result.data.allMarkdownRemark.edges.forEach(edge => {
      createPage({
        path: `${edge.node.frontmatter.slug}`,
        component: blogPostTemplate,
        context: {},
      })
    })
  })
}

EDIT #1

Your error occurs in the page template. You can handle errors in your components the React way, with error boundaries. Wrap an error boundary around your component and handle whatever goes wrong in there. The error boundary can kick off the building of an error page as well. You can handle whatever your page query returns in your PageTemplate component as well.

<PageTemplate>
  <ErrorBoundary>
    <YourContent />
  </ErrorBoundary>
</Page Template>

EDIT #2

I understand the problem now and can offer some suggesstions. I don't think there is an easy solution since it touches the inner workings of both React and Gatsby:

try catch the non React parts of your template. Use wrapper functions that try catch errors.

I assume you have something like this inside your JSX code:

<PageTemplate>
  { imagesSizes.split(',') // do whatever and break the build }
</PageTemplate>

Instead, run all your code breaking variables through functions with try catch first. If any function catch, then you render your error page. You probably need a class base component for this. Place the functions before you call render()

let pageThrows = false;

const imageSizesSplitCheck = (images) => {
  try {
    imagesSizes.split(',') // throw error
  } catch {
    pageThrows = true; // outside of the JSX flow you can still catch errors while serverside renddering
  }
}
// more try catch functions

if (pageThrows) {
  // render error page
} else {
  // render default page 
}


It is good coding practice to handle edge cases of your data so that no exceptions are thrown. I think it is even mentioned in the Clean Code book. Otherwise you miseuse exceptions for the normal program flow. Exception should remain excpetions.

EliteRaceElephant
  • 7,744
  • 4
  • 47
  • 62
  • Thanks for the answer however there is a nuanced detail I had in the question I should've clarified a bit more. I am in fact creating pages programmatically via `createPage` but the error doesn't occur there. It occurs when page renders. By the context of [this question](https://stackoverflow.com/q/59865997/5776910), the only thing I'm querying for during `createPages` are the `id`s of the entity. I then use a [page query](https://www.gatsbyjs.org/docs/page-query/) to pull more data about the entity. – Rico Kahler Jan 26 '20 at 22:21
  • The component makes certain assumptions about the data and inside the component is where it throws and where I want to stop the page from being created or at least replace the page with an error page. – Rico Kahler Jan 26 '20 at 22:22
  • Unfortunately, error boundaries don't work for server-side rendering. [See here](https://reactjs.org/docs/error-boundaries.html#introducing-error-boundaries) and read the note :( – Rico Kahler Jan 26 '20 at 22:57
  • You need to supply more information of your error if you want a helpful answer: error message, the code of your page template component, line where the build breaks, graphQL query etc. – EliteRaceElephant Jan 26 '20 at 23:10
  • I updated my question to clarify what I'm trying to achieve. If you have any ideas on how to achieve what I'm looking for, I'd be extremely grateful. Regardless, thanks for your answer and your edits and I apologize for not asking a more specific question from the get-go. – Rico Kahler Jan 27 '20 at 00:58