Skip to content
This repository has been archived by the owner on Apr 6, 2023. It is now read-only.

feat(pages): add validate hook for definePageMeta #7870

Merged
merged 9 commits into from Oct 10, 2022
9 changes: 9 additions & 0 deletions docs/content/3.api/3.utils/define-page-meta.md
Expand Up @@ -19,6 +19,7 @@
definePageMeta(meta: PageMeta) => void

interface PageMeta {
validate?: (route: RouteLocationNormalized) => boolean | Promise<boolean> | Partial<NuxtError> | Promise<Partial<NuxtError>>
pageTransition?: boolean | TransitionProps
layoutTransition?: boolean | TransitionProps
key?: false | string | ((route: RouteLocationNormalizedLoaded) => string)
Expand Down Expand Up @@ -73,6 +74,14 @@ interface PageMeta {

Define anonymous or named middleware directly within `definePageMeta`. Learn more about [route middleware](/docs/directory-structure/middleware).

**`validate`**

- **Type**: `(route: RouteLocationNormalized) => boolean | Promise<boolean> | Partial<NuxtError> | Promise<Partial<NuxtError>>`

Validate whether a given route can validly be rendered with this page. Return true if it is valid, or false if not. If another match can't be found, this will mean a 404. You can also directly return an object with `statusCode`/`statusMessage` to respond immediately with an error (other matches will not be checked).

:StabilityEdge

**`[key: string]`**

- **Type**: `any`
Expand Down
16 changes: 7 additions & 9 deletions docs/content/migration/7.component-options.md
Expand Up @@ -117,7 +117,9 @@ See [layout migration](/migration/pages-and-layouts).

## `validate`

There is no longer a validate hook in Nuxt 3. Instead, you can create a custom middleware function, or directly throw an error in the setup function of the page.
The validate hook in Nuxt 3 only accepts a single argument, the `route`. Just as in Nuxt 2, you can return a boolean value. If you return false and another match can't be found, this will mean a 404. You can also directly return an object with `statusCode`/`statusMessage` to respond immediately with an error (other matches will not be checked).

:StabilityEdge

```diff [pages/users/[id].vue]
- <script>
Expand All @@ -128,14 +130,10 @@ There is no longer a validate hook in Nuxt 3. Instead, you can create a custom m
- }
+ <script setup>
+ definePageMeta({
+ middleware: [
+ async function (to, from) {
+ const nuxtApp = useNuxtApp()
+ if (!valid) {
+ return abortNavigation('Page not found')
+ }
+ }
+ ]
+ validate: async (route) => {
danielroe marked this conversation as resolved.
Show resolved Hide resolved
+ const nuxtApp = useNuxtApp()
Atinux marked this conversation as resolved.
Show resolved Hide resolved
+ return true // if valid
+ }
+ })
</script>
```
Expand Down
3 changes: 2 additions & 1 deletion packages/nuxt/src/app/plugins/router.ts
Expand Up @@ -234,7 +234,8 @@ export default defineNuxtPlugin<{ route: Route, router: Router }>((nuxtApp) => {
if (process.server) {
if (result === false || result instanceof Error) {
const error = result || createError({
statusMessage: `Route navigation aborted: ${initialURL}`
statusCode: 404,
statusMessage: `Page Not Found: ${initialURL}`
})
return callWithNuxt(nuxtApp, showError, [error])
}
Expand Down
5 changes: 5 additions & 0 deletions packages/nuxt/src/pages/module.ts
Expand Up @@ -50,6 +50,11 @@ export default defineNuxtModule({
if (app.mainComponent!.includes('@nuxt/ui-templates')) {
app.mainComponent = resolve(runtimeDir, 'app.vue')
}
app.middleware.unshift({
name: 'validate',
path: resolve(runtimeDir, 'validate'),
global: true
})
})

// Prerender all non-dynamic page routes when generating app
Expand Down
12 changes: 11 additions & 1 deletion packages/nuxt/src/pages/runtime/composables.ts
@@ -1,8 +1,18 @@
import { KeepAliveProps, TransitionProps, UnwrapRef } from 'vue'
import type { RouteLocationNormalizedLoaded, RouteRecordRedirectOption } from 'vue-router'
import type { RouteLocationNormalized, RouteLocationNormalizedLoaded, RouteRecordRedirectOption } from 'vue-router'
import type { NuxtError } from '#app'

export interface PageMeta {
[key: string]: any
/**
* Validate whether a given route can validly be rendered with this page.
*
* Return true if it is valid, or false if not. If another match can't be found,
* this will mean a 404. You can also directly return an object with
* statusCode/statusMessage to respond immediately with an error (other matches
* will not be checked).
*/
validate?: (route: RouteLocationNormalized) => boolean | Promise<boolean> | Partial<NuxtError> | Promise<Partial<NuxtError>>
/**
* Where to redirect if the route is directly matched. The redirection happens
* before any navigation guard and triggers a new navigation with the new
Expand Down
3 changes: 2 additions & 1 deletion packages/nuxt/src/pages/runtime/router.ts
Expand Up @@ -159,7 +159,8 @@ export default defineNuxtPlugin(async (nuxtApp) => {
if (process.server || (!nuxtApp.payload.serverRendered && nuxtApp.isHydrating)) {
if (result === false || result instanceof Error) {
const error = result || createError({
statusMessage: `Route navigation aborted: ${initialURL}`
statusCode: 404,
statusMessage: `Page Not Found: ${initialURL}`
})
return callWithNuxt(nuxtApp, showError, [error])
}
Expand Down
12 changes: 12 additions & 0 deletions packages/nuxt/src/pages/runtime/validate.ts
@@ -0,0 +1,12 @@
import { createError, defineNuxtRouteMiddleware } from '#app'

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

const result = await Promise.resolve(to.meta.validate(to))
if (typeof result === 'boolean') {
return result
}

return createError(result)
})
5 changes: 5 additions & 0 deletions test/basic.test.ts
Expand Up @@ -65,6 +65,11 @@ describe('pages', () => {
expect(headers.get('location')).toEqual('/')
})

it('validates routes', async () => {
const { status } = await fetch('/forbidden')
expect(status).toEqual(404)
})

it('render 404', async () => {
const html = await $fetch('/not-found')

Expand Down
6 changes: 6 additions & 0 deletions test/fixtures/basic/pages/[...slug].vue
Expand Up @@ -4,3 +4,9 @@
<div>404 at {{ $route.params.slug[0] }}</div>
</div>
</template>

<script setup lang="ts">
definePageMeta({
validate: to => to.path !== '/forbidden'
})
</script>