nuxt: allow manually redirecting to 404 route within a dynamic slug

Environment

------------------------------
- Operating System: `Linux`
- Node Version:     `v16.14.2`
- Nuxt Version:     `3.0.0-rc.1`
- Package Manager:  `npm@7.17.0`
- Builder:          `vite`
- User Config:      `-`
- Runtime Modules:  `-`
- Build Modules:    `-`
------------------------------

Reproduction

https://stackblitz.com/edit/github-yyumyt?file=pages/404.vue

Describe the bug

I have defined three pages (routes):

  • car.vue
  • [slug].vue
  • 404.vue

If I go to http://.../hi => it loads [slug].vue => OK If I go to http://.../car => it loads car.vue => OK If I go to http://.../404 => it loads [slug].vue => FAIL

Additional context

No response

Logs

No response

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 4
  • Comments: 20 (15 by maintainers)

Most upvoted comments

I am positive the broad idea of this issue is to ask “how to handle 404 on dynamic path”.

  const $route = useRoute()
  const { data } = await useAsyncData(`product:${$route.params.id}`, () => $fetch(`/api/products/${$route.params.id}`))

  // TODO: Display proper 404 when accessing unknown product id

  // Requirements:
  // - Using default error page - big "404" with small "Page not found: /product/42" - just like native nuxt 404
  // - On all types of navigation:
  //   a) Home -> Product/42 (client)
  //   b) Home -> Product/42 -> Home -> Product/42 (client + cache)
  //   c) Product/42 (server)
  //   d) Product/42 -> Home -> Product/42 (server into client + cache)
  // - Proper status code and status message when SSR (404, Not Found)

There is currently no documentation on how to properly handle API’s 404 response in the docs. From my experience it is also extremely hard, as all of the 4 navigation types listed in comments above require different logic each.


Here is the repo reproduction in case you want to bootstrap the project: data-fetching

Thanks for that link @Aareksio.

I’ve submitted a related PR to fix an issue, add some basic docs and improve DX (by avoiding import of createError), but check out https://stackblitz.com/edit/github-jwfbey. Does that answer your questions?

If I have logic in setup inside [slug].vue where I call useFetch and the api returns 404, I would like to redirect to the /404 route which is not possible now.

Created two issues to follow:

Feel free to open a new one if you think there’s another one to track.

Minimal reproduction for the above, using script setup for readability: https://stackblitz.com/edit/github-5jhffm

I’m having a related problem rendering a 404 error:

<!-- test404.vue -->
<template>
  <div>Content: {{ data.id }}</div>
</template>

<script lang="ts">
export default defineComponent({
  async setup() {
    const { data, error } = await useAsyncData<{ id: number }>(async () => {
      // URL returns 404, causing fetch to throw an error
      return await $fetch('https://run.mocky.io/v3/b764d683-f7cd-4128-9de0-077d891d3886')
    })
    if (error.value) {
      console.log('error detected')
      throw createError({
        statusCode: 404,
        fatal: true,
      })
    }

    return { data }
  },
})
</script>

The console shows:

error detected
[Vue warn]: Unhandled error during execution of setup function
  at <Test404 onVnodeUnmounted=fn<onVnodeUnmounted> ref=Ref< undefined > >
[Vue warn]: Property "data" was accessed during render but is not defined on instance.
[nuxt] [request error] [unhandled] [500] Cannot read properties of undefined (reading 'id')

So, instead of 404, a 500 error is rendered, because the template is trying to access a property of undefined (data.id). It works (404 rendered) if I remove data.id from the template. Is this the intended behavior?

I’d like to avoid to wrap my whole template into a <div v-if="data"></div> as the template should not be rendered at all (I’m throwing an exception even before the return) and I’m using useAsyncData instead of useLazyAsyncData to not have to handle the empty data situation in the template.

@danielroe the throwError seems to be marked as deprecated now.

Swapping for throw createError seems to output fails in the server console "[nitro] [dev] [unhandledRejection] Error: nuxt instance unavailable

if (!data.value) {
  throw createError({ statusCode: 404, statusMessage: 'Page Not Found' })
}

@danielroe Example: I have a bike shop. A customer accesses the route: http://bike.com => index.vue. The customer wants to view the bike categories http://bike.com/blue-bikes => [slug].vue. The customer selects one of the bikes from the category, http://bike.com/best-blue-bike.html => [slug].html.vue. Everything works great, but how do I catch the condition where I want to redirect to a 404 page?

Minimal reproduction for the above, using script setup for readability: https://stackblitz.com/edit/github-5jhffm

Should I open a separate issue for that?

Edit: Created https://github.com/nuxt/nuxt.js/issues/14695.

We deliberately don’t throw a full page error in this case by default (so you can handle it within the UI with more granular error handlers). But if you want the full error page, you can add fatal: true to the options of createError.

@danielroe Even with your fix, when using SSR the backend still throws internal an error (and flashes it until the hidration takes over):

[nitro] [dev] [unhandledRejection] Error: nuxt instance unavailable
    at Module.useNuxtApp (file:///home/projects/nuxt-starter-nlacpe/.nuxt/dist/server/server.mjs:416:13)
    at Module.clearError (file:///home/projects/nuxt-starter-nlacpe/.nuxt/dist/server/server.mjs:1004:41)
    at eval (file:///home/projects/nuxt-starter-nlacpe/.nuxt/dist/server/server.mjs:3106:33)
    at triggerAfterEach (/home/projects/nuxt-starter-nlacpe/node_modules/vue-router/dist/vue-router.cjs.js:3156:13)
    at eval (/home/projects/nuxt-starter-nlacpe/node_modules/vue-router/dist/vue-router.cjs.js:3059:13)
    at async eval (file:///home/projects/nuxt-starter-nlacpe/.nuxt/dist/server/server.mjs:2584:7)
    at async createNuxtAppServer (file:///home/projects/nuxt-starter-nlacpe/.nuxt/dist/server/server.mjs:60:7)
    at async Object.renderToString (file:///home/projects/nuxt-starter-nlacpe/node_modules/vue-bundle-renderer/dist/index.mjs:277:19)
    at async eval (file:///home/projects/nuxt-starter-nlacpe/.nuxt/dev/index.mjs:476:20)
    at async eval (file:///home/projects/nuxt-starter-nlacpe/node_modules/h3/dist/index.mjs:475:19)

It’s visible in the logs of the example you provided, but here’s a minimal example nontheless: https://stackblitz.com/edit/nuxt-starter-nlacpe

https://user-images.githubusercontent.com/5151543/169717529-9f1a377b-30d9-4466-b576-bab9ffa550ff.mp4

The error is thrown whenever throwError is used after an await in the setup function