Error Handling

As described in Introduction, if an error occurs during server-side rendering, we have two coping strategies: one is to fall back to the SPA mode, and the other is to display a custom error page.

Fall back to SPA mode

Automatic fallback

This is the default behavior of Vapper. When any error occurs during server rendering, Vapper will fall back to SPA mode, which will send the SPA page to the client. If the error is an error that only occurs on the server side, or if the error is a non-fatal error, that means the user can continue to use our app. This makes sense in some scenarios, such as ordering page, payment page, and other scenarios that emphasize conversion rates.

Manually fallback Core 0.8.0+

If you choose Custom Server, and you might write your own business middleware, but Vapper can't catch exceptions thrown by user-written business middleware. So Vapper exposes the vapper.fallbackSPA(req, res) function to manually fallback to the SPA mode so that the user can call this method in their own error handling middleware to manually fallback to SPA mode:

The vapper.fallbackSPA() function takes two parameters: the Nodejs native request object req and the response object res. The following is an example of Koa, showing how to manually fallback to SPA mode when an error occurs.

















 
 
 
 
 
 
 
 
 
 
 
 
 
 












const Koa = require('koa')
const app = new Koa()
const Vapper = require('@vapper/core')

async function starter () {
  const vapper = new Vapper({ mode: process.env.NODE_ENV || 'production' })

  const {
    options: {
      port,
      host
    }
  } = vapper

  await vapper.setup()

  // Your error handling middleware
  app.use(async (ctx, next) => {
    try {
      await next()
    } catch (err) {
      // Manually call the vapper.fallbackSPA() function
      ctx.status = 200
      ctx.respond = false
      vapper.fallbackSPA(ctx.req, ctx.res)
    }
  })

  // Business middleware is written here
  // app.use(...)

  app.use((ctx) => {
    ctx.status = 200
    ctx.respond = false
    vapper.handler(ctx.req, ctx.res)
  })

  app.listen(port, host, () => vapper.logger.info(`Server running at: http://${host}:${port}`))
}

starter()

About how to customize Server Please read: Custom Server.

Custom fallback logic Core 0.13.0+

By default, vapper internally uses serve-static to provide a static resource service. When a user request comes in, vapper will provide the file under dist/ as a static resource to the user. You can configure it via configuration#static, all configuration options are: serve-static#options.

In general, this is fine, but we usually have a separate static resource server or CDN, and our nodejs service becomes a server that only serves the dist/index.html file. Since the size of the dist/index.html file is small, we can read the file into memory when the service starts, and file IO will no longer occur when a request comes. To do this, vapper provides the configuration#fallbackSpaHandler option, which allows you to customize the logic for fallback the SPA, an example:

// 1. Read the dist/index.html file generated by the build into memory when the service starts
const spaHTMLContent = fs.readFileSync(path.resolve(__dirname, '../dist/index.html'), 'utf-8')

// vapper.config.js
module.exports = {
  // Other configurations...

  // Custom fallback SPA logic
  fallbackSpaHandler (req, res) {
    // 2. Send the in-memory string directly to the client
    res.setHeader('Content-Type', 'text/html; charset=UTF-8')
    res.end(spaHTMLContent)
  }
}

Custom error page

Of course, if you want the error page to be displayed to the user when the error occurs, it is very simple.

The vm.error property of the root component

Vapper injects the error attribute to the root component instance, which is an error object that holds the error message. So you can decide what to render by checking if this.error exists, as shown in the following code:











 







// Entry file: src/main.js

export default function createApp () {
  // 1. Create a router instance
  const router = createRouter()

  // 2. Create a app instance
  const app = new Vue({
    router,
    render (h) {
      return this.error ? h('h1', 'error') : h(App)
    }
  })

  // 3. return
  return { app, router }
}

Within the render function of the root component, if this.error exists, the custom content is presented to the user, otherwise the application is rendered normally. You can render anything you want, such as the Error.vue component:

import Error from './Error.vue'

export default function createApp () {
  // 1. Create a router instance
  const router = createRouter()

  // 2. Create a app instance
  const app = new Vue({
    router,
    render (h) {
      return this.error ? h(Error, { props: { error: this.error } }) : h(App)
    }
  })

  // 3. return
  return { app, router }
}

The Error Object

this.error exists only on the root component instance, it is an error object:

{
  url: to.path, // The url where the error occurred
  code: 404,    // Error code
  message: 'Page Not Found' // Error message
}

What you need to know is that, in fact, you can assign arbitrary values to this.error at runtime, but the good practice is to give it the Error object with the same structure as the object shown in the code above.

Capturing errors in routing guards

For complex applications, such as applications that require permission control, it is normal to write the appropriate authentication logic in the routing guard, for example:

router.beforeEach(() => {
  // Some logic
})

What if the code in the routing guard throws an error? We can catch the error using the onError function natively provided by vue-router:








 
 
 
 





 







// Entry file: src/main.js

export default function createApp () {
  // 1. Create a router instance
  const router = createRouter()

  // Use `router.onError` to catch routing errors
  router.onError((err) => {
    // Assign the err object to the vm.error property of the root component instance
    router.app.error = err
  })

  // 2. Create a app instance
  const app = new Vue({
    router,
    render (h) {
      return this.error ? h('h1', 'error') : h(App)
    }
  })

  // 3. return
  return { app, router }
}

router.onError is temporarily unable to capture Promises rejections, please see: [https://github.com/vuejs/vue-router/issues/2833] (https://github.com/vuejs/vue-router/issues/2833).

Asynchronous error handling