nuxt / bridge

🌉 Experience Nuxt 3 features on existing Nuxt 2 projects
MIT License
273 stars 29 forks source link

SSR: Error handling with useLazyAsyncData composable #944

Closed evgenii-code closed 1 year ago

evgenii-code commented 1 year ago

Environment

Reproduction

Repository https://github.com/evgenii-code/nuxt-bridge-error-page

Reproduction in sandbox
https://codesandbox.io/p/sandbox/nuxt-bridge--uselazyasyncdata-with-error-ddcrgq

Describe the bug

I want to redirect to an error page when an error occurs by fetching data with useLazyAsyncData composable.
We can't use await useLazyAsyncData in <script setup> with Vue 2 and the data is fetching in lazy mode. So the redirect happens only on client side, when error.value === true

There you can see how redirect occurs only on client side -- background flashes for a moment.

Video reproduction https://codesandbox.io/p/sandbox/nuxt-bridge--uselazyasyncdata-with-error-ddcrgq

It is mandatory to redirect on server side to the error page if an error occurs. I can't understand how to achieve this behavior

Additional context

I can achieve wanted behavior by using asyncData in the Options API, but since we want to update out project to Nuxt 3, we want to use Composition API.

Logs

No response

wattanx commented 1 year ago

Sorry, Composables that block Navigation like asyncData cannot be implemented in nuxt 2. refs: https://github.com/nuxt-community/composition-api/issues/596#issuecomment-1008972575

As a side note, asyncData can also be used in Nuxt 3. https://nuxt.com/docs/api/utils/define-nuxt-component#asyncdata

evgenii-code commented 1 year ago

@wattanx Thank you for the quick response and provided recources,
I get that we can't block navigation with composition API in Nuxt 2.

But I think my original question was formulated incorrectly.

I want to get an error from the response, because I see the HTML rendered on the server with an error. I was wondering, is there some kind of a hook triggering after useLazyAsyncData finishes that I can intercept error on server side?

Like

const { hook } = useNuxtApp();
const { data } = useLazyAsyncData(...);

hook('whatever:hook', ssrErrorHandler);

This is my approach to handle error on my own, but it feels wrong.
Maybe I should give up and use Options API instead

<script setup>
const { $axios } = useNuxtApp()
const { data, pending } = useLazyAsyncData('fetch-todos', async () => {
  try {
    return {
      response: await $axios.$get(
        'https://jsonplaceholder.typicode.com/todos/-1'
      ),
      error: null,
    }
  } catch (error) {
    return { response: null, error }
  }
})
</script>
lukas-pierce commented 1 week ago

Create composable lib/page-error-handler.ts:

import type { NuxtError } from '#app/composables/error'

export const useHandlePageError = (error: Ref<NuxtError | null>) => {

  // universal way to handle serverside and clientside error
  // https://www.reddit.com/r/Nuxt/comments/17coll0/comment/kg4syhv/
  watch(error, (value) => {
    if (!value) { return }
    throw createError({
      statusCode: error.value?.statusCode || 400,
      message: error.value?.data?.message || error.value?.message || 'Something went wrong',
      fatal: true,
    })
  }, { immediate: process.server })

}

Then use it on your page:

<template>
  <!--
  add if condition because data is a nullable ref,
  when execute on clientside with error (or while pending)
  it has null value
  -->
  <div v-if="data">{{ data }}</div>
</template>

<script setup>
import { useHandlePageError } from '~/lib/page-error-handler'

const { data, error } = await useLazyAsyncData('my_page', async () => {
  // fetch your data
})

/*
In client execution we will get here immediately,
because `lazy` does not wait for the request to be executed.
The error will initially have a null value, and only after
the request is completed then the error will get its value.
Therefore `watch(error)` is used

In server execution lazy is ignored, and we will get
here only after the request is executed,
therefore immediate is used for the server variant
*/
useHandlePageError(error)
</script>