如何捕获 Nuxt.js 中的服务器错误,以免页面呈现崩溃? (视点)

How to catch server errors in Nuxt.js so it doesn't crash page render? (Vue)

上下文

这个问题与我的另一个问题有关,How to handle apollo client errors crashing page render in Nuxt? ,但我会尽量保持孤立,因为我希望这个问题只关注 Nuxt(减去 apollo)。但是,我决定单独询问这个问题,因为我正在寻找一个完全不同的 response/solution.

问题

我目前正在维护一个生产 Nuxt/Vue 应用程序,它使用 @nuxt/apollo 模块来发出 GraphQL 请求。

问题是,我们依赖的 GraphQL 服务器有时会宕机,returns 出现 HTML 错误页面,这会导致 Apollo 客户端崩溃。但是因为我们将 Apollo 作为 nuxt 模块加载,它也会使页面渲染管道崩溃。给我们一个看起来像这样的通用服务器错误页面;

Server error An error occurred in the application and your page could not be served. If you are the application owner, check your logs for details.

以及以下堆栈跟踪:

 ERROR  Network error: Unexpected token < in JSON at position 0                                                            08:11:04

  at new ApolloError (node_modules/apollo-client/bundle.umd.js:92:26)
  at node_modules/apollo-client/bundle.umd.js:1588:34
  at node_modules/apollo-client/bundle.umd.js:2008:15
  at Set.forEach (<anonymous>)
  at node_modules/apollo-client/bundle.umd.js:2006:26
  at Map.forEach (<anonymous>)
  at QueryManager.broadcastQueries (node_modules/apollo-client/bundle.umd.js:2004:20)
  at node_modules/apollo-client/bundle.umd.js:1483:29
  at processTicksAndRejections (node:internal/process/task_queues:94:5)

但是,none 这个堆栈跟踪让我们可以看到 nuxt 在哪里抛出错误,所以我们可以处理它。

我们尝试了什么

在过去的几周里,我们用尽了所有方法来调查这个问题。我们首先尝试通过使用所有 3 个 apollo 库抽象的错误处理解决方案直接在 Apollo 级别处理错误来解决它:

如果您想阅读更多相关内容(即使它与这个问题无关),您可以阅读更多关于我的原始问题 here

但是,现在我更想知道是否有办法以某种方式处理这些页面呈现错误:

由于我们目前使用的 apollo nuxt 模块无法正常工作,我想知道 Nuxt 是否支持某种方式来处理错误。

Nuxt 的文档在错误处理方面非常有限,这并没有多大帮助。充其量,它包含有关错误页面以及如何使用 context.error 重定向到错误页面的信息。但是它没有关于如何捕获常见错误的专门页面。我感觉 Nuxt hooks 可能是答案,但关于它们的文档很难浏览而且也很少。

我找到的关于 nuxt 错误处理的最完整的信息来源是这篇文章,Error handling in NuxtJS,其中没有任何建议对我们有用。

总结

当我们使用的 @nuxt/apollo nuxt 模块崩溃时,我们的 nuxt 应用程序崩溃。我们想知道是否有某种标准的 nuxt 方法来捕获它,或者唯一可能的解决方案是否只是将我们的整个应用程序迁移到不使用 @nuxt/apollo 模块并使用 ES6 promise 语法并加载 apollo-client 作为未深入集成到 nuxt 生命周期中的独立库手动添加到应用程序中。

编辑: 我自己认为问题出在 Vue Apollo 插件或 Nuxt Apollo 模块的某处,以及那里的错误处理方式。我认为您可以直接在 Apollo 模块中处理错误,但在 SSR 中这是不可能的。

您必须记住,对于 CSR 和 SSR,您可能需要另一种解决方案。

简而言之,renderRoute 失败了,因为这个 SSR 最终变成了 Nuxt 的默认 errorMiddleware

1: 直接调用 Apollo 查询,这使您可以完全控制该页面的错误处理,并且适用于 CSR 和 SSR

export default {
  mounted() {
    if (!this.books.length) {
      // client side
      this.fetchBooks()
    }
  },

  serverPrefetch() {
    this.fetchBooks()
  },
  methods: {
    fetchBooks() {
      this.$apollo
        .query({
          query: gql`
            query books {
              books {
                title
                author
                test
              }
            }
          `,
        })
        .catch((e) => {
          console.log(e)
        })
        .then((data) => {
          /// set books
        })
    },
  },
}

2: 添加一个 errorMiddleware 钩子并在那里处理错误。这仅适用于 SSR。重要的是要了解呈现失败,因此您必须重定向或呈现另一个页面。

//nuxt.config.js
 hooks: {
    render: {
      errorMiddleware(app) {
        app.use((error, req, res, next) => {
          res.writeHead(307, {
            Location: '/network-error',
          })
          res.end()
        })
      },
    },
  },

3: Return 在Apollo的error方法中为false,仅对CSR有效

export default {
  apollo: {
    books: {
      query() {
        return gql`
          query books {
            books {
              title
              author
              test
            }
          }
        `
      },
      error() {
        return false
      },
    },
  },
}

为了防止页面崩溃,您需要处理错误并将它们隔离,以免它们影响其他组件的渲染。这可以通过 ErrorBoundary 的错误处理概念来实现。您可以创建一个通用组件来重用 ErrorBoundary 逻辑。这种方法还有其他几个好处。

  • 有助于使组件免受错误处理逻辑的影响
  • 允许我们使用声明式组件组合而不是依赖命令式try/catch
  • 我们可以随心所欲地使用它——包装单个组件或整个应用程序部分。

下面是如何创建错误边界的示例。

export default {
  name: 'ErrorBoundary',
  data: () => ({
    error: false
  }),
  errorCaptured (err, vm, info) {
    this.error = true
  },
  render (h) {
    return this.error ? h('p', 'Something went wrong') : this.$slots.default[0]
  }
}

现在,您可以将任何组件包装到错误边界并按照给定的方式隔离错误,

<error-boundary>
  <counter />
</error-boundary>

ErrorBoundary 组件的注意事项

使用 errorCaptured 挂钩时有一些注意事项。目前,错误仅捕获于:

  • 渲染函数
  • 观察者回调
  • 生命周期挂钩
  • 组件事件处理程序