Skip to content

Commit

Permalink
fix(nuxt): show client error if no page matches after validate fails …
Browse files Browse the repository at this point in the history
…(#18978)
  • Loading branch information
danielroe committed Feb 16, 2023
1 parent 4b2901b commit 7d0ecb5
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 7 deletions.
31 changes: 29 additions & 2 deletions packages/nuxt/src/pages/runtime/validate.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,38 @@
import { defineNuxtRouteMiddleware } from '#app/composables/router'
import { createError, showError } from '#app/composables/error'
import { callWithNuxt, useNuxtApp } from '#app/nuxt'
import { defineNuxtRouteMiddleware, useRouter } from '#app/composables/router'

export default defineNuxtRouteMiddleware(async (to) => {
if (!to.meta?.validate) { return }

const nuxtApp = useNuxtApp()
const router = useRouter()

const result = await Promise.resolve(to.meta.validate(to))
if (result === true) {
return
}
return result
if (process.server) {
return result
}
const error = createError({
statusCode: 404,
statusMessage: `Page Not Found: ${to.fullPath}`
})
const unsub = router.beforeResolve((final) => {
unsub()
if (final === to) {
const unsub = router.afterEach(async () => {
unsub()
await callWithNuxt(nuxtApp, showError, [error])
// We pretend to have navigated to the invalid route so
// that the user can return to the previous page with
// the back button.
window.history.pushState({}, '', to.fullPath)
})
// We stop the navigation immediately before it resolves
// if there is no other route matching it.
return false
}
})
})
14 changes: 12 additions & 2 deletions test/basic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,16 @@ describe('pages', () => {
it('validates routes', async () => {
const { status } = await fetch('/forbidden')
expect(status).toEqual(404)

const page = await createPage('/navigate-to-forbidden')
await page.waitForLoadState('networkidle')
await page.getByText('should throw a 404 error').click()
expect(await page.getByRole('heading').textContent()).toMatchInlineSnapshot('"Page Not Found: /forbidden"')

page.goto(url('/navigate-to-forbidden'))
await page.waitForLoadState('networkidle')
await page.getByText('should be caught by catchall').click()
expect(await page.getByRole('heading').textContent()).toMatchInlineSnapshot('"[...slug].vue"')
})

it('render 404', async () => {
Expand All @@ -107,7 +117,7 @@ describe('pages', () => {
// expect(html).toMatchInlineSnapshot()

expect(html).toContain('[...slug].vue')
expect(html).toContain('404 at not-found')
expect(html).toContain('catchall at not-found')

// Middleware still runs after validation: https://github.com/nuxt/nuxt/issues/15650
expect(html).toContain('Middleware ran: true')
Expand Down Expand Up @@ -941,7 +951,7 @@ describe.runIf(isDev() && !isWebpack)('vite plugins', () => {
expect(await $fetch('/__nuxt-test')).toBe('vite-plugin with __nuxt prefix')
})
it('does not allow direct access to nuxt source folder', async () => {
expect(await $fetch('/app.config')).toContain('404')
expect(await $fetch('/app.config')).toContain('catchall at')
})
})

Expand Down
15 changes: 15 additions & 0 deletions test/fixtures/basic/error.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<template>
<div>
<div>
<h1>{{ error?.message }}</h1>
This is the error page 😱
</div>
</div>
</template>

<script setup lang="ts">
import type { NuxtError } from '#app'
defineProps({
error: Object as () => NuxtError
})
</script>
4 changes: 2 additions & 2 deletions test/fixtures/basic/pages/[...slug].vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<div>
<div>[...slug].vue</div>
<div>404 at {{ $route.params.slug[0] }}</div>
<h1>[...slug].vue</h1>
<div>catchall at {{ $route.params.slug[0] }}</div>
<div>Middleware ran: {{ !!($route.meta.override as any)?.includes('extended middleware') }}</div>
</div>
</template>
Expand Down
18 changes: 18 additions & 0 deletions test/fixtures/basic/pages/navigate-to-forbidden.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<template>
<div>
<div>navigate-to-forbidden.vue</div>
<NuxtLink to="/forbidden">
should throw a 404 error
</NuxtLink>
<NuxtLink to="/some-404">
should be caught by catchall
</NuxtLink>
</div>
</template>

<script setup lang="ts">
definePageMeta({
middleware: ['override'],
validate: to => to.path !== '/forbidden'
})
</script>
2 changes: 1 addition & 1 deletion test/hmr.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ if (process.env.TEST_ENV !== 'built' && !isWindows) {

it('should detect new routes', async () => {
const html = await $fetch('/some-404')
expect(html).toContain('404 at some-404')
expect(html).toContain('catchall at some-404')

// write new page route
const indexVue = await fsp.readFile(join(fixturePath, 'pages/index.vue'), 'utf8')
Expand Down

0 comments on commit 7d0ecb5

Please sign in to comment.