mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-22 05:35:13 +00:00
feat(pages): add validate
hook for definePageMeta
(#7870)
This commit is contained in:
parent
75f4a54f7e
commit
829a550580
@ -117,3 +117,25 @@ definePageMeta({
|
|||||||
::
|
::
|
||||||
|
|
||||||
:ReadMore{link="/guide/directory-structure/middleware"}
|
:ReadMore{link="/guide/directory-structure/middleware"}
|
||||||
|
|
||||||
|
## Route Validation
|
||||||
|
|
||||||
|
Nuxt offers route validation via the `validate` property in [`definePageMeta`](/api/utils/define-page-meta) in each page you wish to validate.
|
||||||
|
|
||||||
|
The `validate` property accepts the `route` as an argument. You can return a boolean value to determine whether or not this is a valid route to be rendered with this page. 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).
|
||||||
|
|
||||||
|
If you have a more complex use case, then you can use anonymous route middleware instead.
|
||||||
|
|
||||||
|
:StabilityEdge
|
||||||
|
|
||||||
|
```vue [pages/post/[id].vue]
|
||||||
|
<script setup>
|
||||||
|
definePageMeta({
|
||||||
|
validate: async (route) => {
|
||||||
|
const nuxtApp = useNuxtApp()
|
||||||
|
// Check if the id is made up of digits
|
||||||
|
return /^\d+$/.test(params.id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
@ -23,6 +23,7 @@ title: "definePageMeta"
|
|||||||
definePageMeta(meta: PageMeta) => void
|
definePageMeta(meta: PageMeta) => void
|
||||||
|
|
||||||
interface PageMeta {
|
interface PageMeta {
|
||||||
|
validate?: (route: RouteLocationNormalized) => boolean | Promise<boolean> | Partial<NuxtError> | Promise<Partial<NuxtError>>
|
||||||
redirect?: RouteRecordRedirectOption
|
redirect?: RouteRecordRedirectOption
|
||||||
alias?: string | string[]
|
alias?: string | string[]
|
||||||
pageTransition?: boolean | TransitionProps
|
pageTransition?: boolean | TransitionProps
|
||||||
@ -79,20 +80,14 @@ interface PageMeta {
|
|||||||
|
|
||||||
Define anonymous or named middleware directly within `definePageMeta`. Learn more about [route middleware](/docs/directory-structure/middleware).
|
Define anonymous or named middleware directly within `definePageMeta`. Learn more about [route middleware](/docs/directory-structure/middleware).
|
||||||
|
|
||||||
**`redirect`**
|
**`validate`**
|
||||||
|
|
||||||
- **Type**: [`RouteRecordRedirectOption`](https://router.vuejs.org/guide/essentials/redirect-and-alias.html#redirect-and-alias)
|
- **Type**: `(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 target location.
|
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
|
:StabilityEdge
|
||||||
|
|
||||||
**`alias`**
|
|
||||||
|
|
||||||
- **Type**: `string | string[]`
|
|
||||||
|
|
||||||
Aliases for the record. Allows defining extra paths that will behave like a copy of the record. Allows having paths shorthands like `/users/:id` and `/u/:id`. All `alias` and `path` values must share the same params.
|
|
||||||
|
|
||||||
**`[key: string]`**
|
**`[key: string]`**
|
||||||
|
|
||||||
- **Type**: `any`
|
- **Type**: `any`
|
||||||
|
@ -112,25 +112,23 @@ See [layout migration](/migration/pages-and-layouts).
|
|||||||
|
|
||||||
## `validate`
|
## `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]
|
```diff [pages/users/[id].vue]
|
||||||
- <script>
|
- <script>
|
||||||
- export default {
|
- export default {
|
||||||
- async validate({ params, query, store }) {
|
- async validate({ params }) {
|
||||||
- return true // if valid
|
- return /^\d+$/.test(params.id)
|
||||||
- }
|
- }
|
||||||
- }
|
- }
|
||||||
+ <script setup>
|
+ <script setup>
|
||||||
+ definePageMeta({
|
+ definePageMeta({
|
||||||
+ middleware: [
|
+ validate: async (route) => {
|
||||||
+ async function (to, from) {
|
+ const nuxtApp = useNuxtApp()
|
||||||
+ const nuxtApp = useNuxtApp()
|
+ return /^\d+$/.test(params.id)
|
||||||
+ if (!valid) {
|
+ }
|
||||||
+ return abortNavigation('Page not found')
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+ ]
|
|
||||||
+ })
|
+ })
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
@ -234,7 +234,8 @@ export default defineNuxtPlugin<{ route: Route, router: Router }>((nuxtApp) => {
|
|||||||
if (process.server) {
|
if (process.server) {
|
||||||
if (result === false || result instanceof Error) {
|
if (result === false || result instanceof Error) {
|
||||||
const error = result || createError({
|
const error = result || createError({
|
||||||
statusMessage: `Route navigation aborted: ${initialURL}`
|
statusCode: 404,
|
||||||
|
statusMessage: `Page Not Found: ${initialURL}`
|
||||||
})
|
})
|
||||||
return callWithNuxt(nuxtApp, showError, [error])
|
return callWithNuxt(nuxtApp, showError, [error])
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,11 @@ export default defineNuxtModule({
|
|||||||
if (app.mainComponent!.includes('@nuxt/ui-templates')) {
|
if (app.mainComponent!.includes('@nuxt/ui-templates')) {
|
||||||
app.mainComponent = resolve(runtimeDir, 'app.vue')
|
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
|
// Prerender all non-dynamic page routes when generating app
|
||||||
|
@ -1,8 +1,18 @@
|
|||||||
import { KeepAliveProps, TransitionProps, UnwrapRef } from 'vue'
|
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 {
|
export interface PageMeta {
|
||||||
[key: string]: any
|
[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
|
* Where to redirect if the route is directly matched. The redirection happens
|
||||||
* before any navigation guard and triggers a new navigation with the new
|
* before any navigation guard and triggers a new navigation with the new
|
||||||
|
@ -159,7 +159,8 @@ export default defineNuxtPlugin(async (nuxtApp) => {
|
|||||||
if (process.server || (!nuxtApp.payload.serverRendered && nuxtApp.isHydrating)) {
|
if (process.server || (!nuxtApp.payload.serverRendered && nuxtApp.isHydrating)) {
|
||||||
if (result === false || result instanceof Error) {
|
if (result === false || result instanceof Error) {
|
||||||
const error = result || createError({
|
const error = result || createError({
|
||||||
statusMessage: `Route navigation aborted: ${initialURL}`
|
statusCode: 404,
|
||||||
|
statusMessage: `Page Not Found: ${initialURL}`
|
||||||
})
|
})
|
||||||
return callWithNuxt(nuxtApp, showError, [error])
|
return callWithNuxt(nuxtApp, showError, [error])
|
||||||
}
|
}
|
||||||
|
12
packages/nuxt/src/pages/runtime/validate.ts
Normal file
12
packages/nuxt/src/pages/runtime/validate.ts
Normal file
@ -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)
|
||||||
|
})
|
@ -65,6 +65,11 @@ describe('pages', () => {
|
|||||||
expect(headers.get('location')).toEqual('/')
|
expect(headers.get('location')).toEqual('/')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('validates routes', async () => {
|
||||||
|
const { status } = await fetch('/forbidden')
|
||||||
|
expect(status).toEqual(404)
|
||||||
|
})
|
||||||
|
|
||||||
it('render 404', async () => {
|
it('render 404', async () => {
|
||||||
const html = await $fetch('/not-found')
|
const html = await $fetch('/not-found')
|
||||||
|
|
||||||
|
6
test/fixtures/basic/pages/[...slug].vue
vendored
6
test/fixtures/basic/pages/[...slug].vue
vendored
@ -4,3 +4,9 @@
|
|||||||
<div>404 at {{ $route.params.slug[0] }}</div>
|
<div>404 at {{ $route.params.slug[0] }}</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
definePageMeta({
|
||||||
|
validate: to => to.path !== '/forbidden'
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
Loading…
Reference in New Issue
Block a user