From bf9ed58c7c28340ebf588a3d1aa723133e90e3df Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 27 Sep 2022 16:39:43 +0100 Subject: [PATCH 1/7] feat(pages): add `validate` hook for `definePageMeta` --- docs/content/migration/7.component-options.md | 14 ++++++-------- packages/nuxt/src/app/plugins/router.ts | 3 ++- packages/nuxt/src/pages/module.ts | 5 +++++ packages/nuxt/src/pages/runtime/composables.ts | 12 +++++++++++- packages/nuxt/src/pages/runtime/router.ts | 3 ++- packages/nuxt/src/pages/runtime/validate.ts | 12 ++++++++++++ test/basic.test.ts | 5 +++++ test/fixtures/basic/pages/[...slug].vue | 6 ++++++ 8 files changed, 49 insertions(+), 11 deletions(-) create mode 100644 packages/nuxt/src/pages/runtime/validate.ts diff --git a/docs/content/migration/7.component-options.md b/docs/content/migration/7.component-options.md index 49f03dcf7e1..7307c774f3c 100644 --- a/docs/content/migration/7.component-options.md +++ b/docs/content/migration/7.component-options.md @@ -117,7 +117,7 @@ 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`. ```diff [pages/users/[id].vue] - ``` diff --git a/packages/nuxt/src/app/plugins/router.ts b/packages/nuxt/src/app/plugins/router.ts index 46ed26c2898..abb3ad4f779 100644 --- a/packages/nuxt/src/app/plugins/router.ts +++ b/packages/nuxt/src/app/plugins/router.ts @@ -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]) } diff --git a/packages/nuxt/src/pages/module.ts b/packages/nuxt/src/pages/module.ts index 0388c3a032d..2975d76e533 100644 --- a/packages/nuxt/src/pages/module.ts +++ b/packages/nuxt/src/pages/module.ts @@ -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 diff --git a/packages/nuxt/src/pages/runtime/composables.ts b/packages/nuxt/src/pages/runtime/composables.ts index 2b6e0e67303..3756f5643b6 100644 --- a/packages/nuxt/src/pages/runtime/composables.ts +++ b/packages/nuxt/src/pages/runtime/composables.ts @@ -1,8 +1,18 @@ +import { NuxtError } from '#app' import { KeepAliveProps, TransitionProps, UnwrapRef } from 'vue' -import type { RouteLocationNormalizedLoaded, RouteRecordRedirectOption } from 'vue-router' +import type { RouteLocationNormalized, RouteLocationNormalizedLoaded, RouteRecordRedirectOption } from 'vue-router' 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 | Partial | Promise> /** * Where to redirect if the route is directly matched. The redirection happens * before any navigation guard and triggers a new navigation with the new diff --git a/packages/nuxt/src/pages/runtime/router.ts b/packages/nuxt/src/pages/runtime/router.ts index 03bc591227c..15dc5bc0742 100644 --- a/packages/nuxt/src/pages/runtime/router.ts +++ b/packages/nuxt/src/pages/runtime/router.ts @@ -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]) } diff --git a/packages/nuxt/src/pages/runtime/validate.ts b/packages/nuxt/src/pages/runtime/validate.ts new file mode 100644 index 00000000000..1561a6ce5c1 --- /dev/null +++ b/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) +}) diff --git a/test/basic.test.ts b/test/basic.test.ts index 2fe9c019dba..d20368b9f98 100644 --- a/test/basic.test.ts +++ b/test/basic.test.ts @@ -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') diff --git a/test/fixtures/basic/pages/[...slug].vue b/test/fixtures/basic/pages/[...slug].vue index 47a02c1123a..a805ed7aea9 100644 --- a/test/fixtures/basic/pages/[...slug].vue +++ b/test/fixtures/basic/pages/[...slug].vue @@ -4,3 +4,9 @@
404 at {{ $route.params.slug[0] }}
+ + From 433e9a10ebc88355910893167272b26caa22df71 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 27 Sep 2022 16:44:08 +0100 Subject: [PATCH 2/7] style: fix lint errors --- packages/nuxt/src/pages/runtime/composables.ts | 6 +++--- packages/nuxt/src/pages/runtime/validate.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/nuxt/src/pages/runtime/composables.ts b/packages/nuxt/src/pages/runtime/composables.ts index 3756f5643b6..ca977dd23ed 100644 --- a/packages/nuxt/src/pages/runtime/composables.ts +++ b/packages/nuxt/src/pages/runtime/composables.ts @@ -1,14 +1,14 @@ -import { NuxtError } from '#app' import { KeepAliveProps, TransitionProps, UnwrapRef } from 'vue' 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 + * 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). */ diff --git a/packages/nuxt/src/pages/runtime/validate.ts b/packages/nuxt/src/pages/runtime/validate.ts index 1561a6ce5c1..14d5a8aeddc 100644 --- a/packages/nuxt/src/pages/runtime/validate.ts +++ b/packages/nuxt/src/pages/runtime/validate.ts @@ -1,7 +1,7 @@ -import { createError, defineNuxtRouteMiddleware } from "#app" +import { createError, defineNuxtRouteMiddleware } from '#app' -export default defineNuxtRouteMiddleware(async to => { - if (!to.meta?.validate) return +export default defineNuxtRouteMiddleware(async (to) => { + if (!to.meta?.validate) { return } const result = await Promise.resolve(to.meta.validate(to)) if (typeof result === 'boolean') { From 3b750dc482ba53c341fbf02882574d9f6358ecfb Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Wed, 28 Sep 2022 10:34:45 +0100 Subject: [PATCH 3/7] docs: add more info and update `definePageMeta` api --- docs/content/3.api/3.utils/define-page-meta.md | 7 +++++++ docs/content/migration/7.component-options.md | 6 ++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/content/3.api/3.utils/define-page-meta.md b/docs/content/3.api/3.utils/define-page-meta.md index 570524b8f3d..fd415b3803f 100644 --- a/docs/content/3.api/3.utils/define-page-meta.md +++ b/docs/content/3.api/3.utils/define-page-meta.md @@ -19,6 +19,7 @@ definePageMeta(meta: PageMeta) => void interface PageMeta { + validate?: (route: RouteLocationNormalized) => boolean | Promise | Partial | Promise> pageTransition?: boolean | TransitionProps layoutTransition?: boolean | TransitionProps key?: false | string | ((route: RouteLocationNormalizedLoaded) => string) @@ -73,6 +74,12 @@ 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 | Partial | Promise>` + + 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). + **`[key: string]`** - **Type**: `any` diff --git a/docs/content/migration/7.component-options.md b/docs/content/migration/7.component-options.md index 7307c774f3c..251f8ec2246 100644 --- a/docs/content/migration/7.component-options.md +++ b/docs/content/migration/7.component-options.md @@ -117,7 +117,7 @@ See [layout migration](/migration/pages-and-layouts). ## `validate` -The validate hook in Nuxt 3 only accepts a single argument, the `route`. +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). ```diff [pages/users/[id].vue] - From e711aa4f1f08f16207dcc43e0239551092694ccb Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Wed, 28 Sep 2022 10:46:44 +0100 Subject: [PATCH 4/7] docs: add edge stability --- docs/content/3.api/3.utils/define-page-meta.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/content/3.api/3.utils/define-page-meta.md b/docs/content/3.api/3.utils/define-page-meta.md index fd415b3803f..c52f933d925 100644 --- a/docs/content/3.api/3.utils/define-page-meta.md +++ b/docs/content/3.api/3.utils/define-page-meta.md @@ -80,6 +80,8 @@ interface PageMeta { 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` From 58437a96119e0b39b2d703476ad4f372a36cd66e Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Wed, 28 Sep 2022 10:47:29 +0100 Subject: [PATCH 5/7] docs: add stability edge to migration docs too --- docs/content/migration/7.component-options.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/content/migration/7.component-options.md b/docs/content/migration/7.component-options.md index 251f8ec2246..2cc80c9ce62 100644 --- a/docs/content/migration/7.component-options.md +++ b/docs/content/migration/7.component-options.md @@ -119,6 +119,8 @@ See [layout migration](/migration/pages-and-layouts). 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] - From 79570d73fa6bbf181484fc7fad6812be7c688765 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Thu, 29 Sep 2022 09:34:40 +0100 Subject: [PATCH 7/7] docs: add route validation section to routing guide --- docs/content/1.getting-started/5.routing.md | 22 +++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/content/1.getting-started/5.routing.md b/docs/content/1.getting-started/5.routing.md index ac3f40c8c8d..b9657f318be 100644 --- a/docs/content/1.getting-started/5.routing.md +++ b/docs/content/1.getting-started/5.routing.md @@ -113,3 +113,25 @@ definePageMeta({ :: :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] + +```