>
/**
* 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..14d5a8aeddc
--- /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/packages/schema/package.json b/packages/schema/package.json
index 849050ceaeb..553bc596110 100644
--- a/packages/schema/package.json
+++ b/packages/schema/package.json
@@ -18,7 +18,7 @@
"@types/semver": "^7",
"@vitejs/plugin-vue": "^3.1.2",
"unbuild": "latest",
- "vite": "~3.1.6"
+ "vite": "~3.1.7"
},
"dependencies": {
"c12": "^0.2.13",
diff --git a/packages/schema/src/config/_app.ts b/packages/schema/src/config/_app.ts
index a11bdd5e087..46adadd8948 100644
--- a/packages/schema/src/config/_app.ts
+++ b/packages/schema/src/config/_app.ts
@@ -51,10 +51,14 @@ export default defineUntypedSchema({
* NUXT_APP_BASE_URL=/prefix/ node .output/server/index.mjs
* ```
*/
- baseURL: process.env.NUXT_APP_BASE_URL || '/',
+ baseURL: {
+ $resolve: async (val) => val || process.env.NUXT_APP_BASE_URL || '/',
+ },
/** The folder name for the built site assets, relative to `baseURL` (or `cdnURL` if set). This is set at build time and should not be customized at runtime. */
- buildAssetsDir: process.env.NUXT_APP_BUILD_ASSETS_DIR || '/_nuxt/',
+ buildAssetsDir: {
+ $resolve: async (val) => val || process.env.NUXT_APP_BUILD_ASSETS_DIR || '/_nuxt/',
+ },
/**
* The folder name for the built site assets, relative to `baseURL` (or `cdnURL` if set).
diff --git a/packages/schema/src/config/_common.ts b/packages/schema/src/config/_common.ts
index 903a3ec82ab..ed043c8d541 100644
--- a/packages/schema/src/config/_common.ts
+++ b/packages/schema/src/config/_common.ts
@@ -100,6 +100,18 @@ export default defineUntypedSchema({
$resolve: async (val, get) => resolve(await get('rootDir'), val || '.')
},
+ /**
+ * Define the server directory of your Nuxt application, where Nitro
+ * routes, middleware and plugins are kept.
+ *
+ * If a relative path is specified, it will be relative to your `rootDir`.
+ *
+ * @version 3
+ */
+ serverDir: {
+ $resolve: async (val, get) => resolve(await get('rootDir'), val || resolve(await get('srcDir'), 'server'))
+ },
+
/**
* Define the directory where your built Nuxt files will be placed.
*
diff --git a/packages/vite/package.json b/packages/vite/package.json
index a6809b40f74..7fa81be96bd 100644
--- a/packages/vite/package.json
+++ b/packages/vite/package.json
@@ -20,7 +20,7 @@
},
"dependencies": {
"@nuxt/kit": "3.0.0-rc.11",
- "@rollup/plugin-replace": "^4.0.0",
+ "@rollup/plugin-replace": "^5.0.0",
"@vitejs/plugin-vue": "^3.1.2",
"@vitejs/plugin-vue-jsx": "^2.0.1",
"autoprefixer": "^10.4.12",
@@ -35,7 +35,7 @@
"get-port-please": "^2.6.1",
"h3": "^0.7.21",
"knitwork": "^0.1.2",
- "magic-string": "^0.26.6",
+ "magic-string": "^0.26.7",
"mlly": "^0.5.16",
"ohash": "^0.1.5",
"pathe": "^0.3.9",
@@ -47,8 +47,8 @@
"rollup": "^2.79.1",
"rollup-plugin-visualizer": "^5.8.2",
"ufo": "^0.8.5",
- "unplugin": "^0.9.2",
- "vite": "~3.1.6",
+ "unplugin": "^0.9.6",
+ "vite": "~3.1.7",
"vite-node": "^0.24.0",
"vite-plugin-checker": "^0.5.1",
"vue-bundle-renderer": "^0.4.3"
diff --git a/packages/vite/src/client.ts b/packages/vite/src/client.ts
index 9f7df783b45..76e71183c94 100644
--- a/packages/vite/src/client.ts
+++ b/packages/vite/src/client.ts
@@ -5,8 +5,7 @@ import viteJsxPlugin from '@vitejs/plugin-vue-jsx'
import type { Connect, ServerOptions } from 'vite'
import { logger } from '@nuxt/kit'
import { getPort } from 'get-port-please'
-import { joinURL, withLeadingSlash, withoutLeadingSlash, withTrailingSlash } from 'ufo'
-import escapeRE from 'escape-string-regexp'
+import { joinURL, withoutLeadingSlash } from 'ufo'
import defu from 'defu'
import type { OutputOptions } from 'rollup'
import { cacheDirPlugin } from './plugins/cache-dir'
@@ -110,12 +109,12 @@ export async function buildClient (ctx: ViteBuildContext) {
const viteServer = await vite.createServer(clientConfig)
ctx.clientServer = viteServer
await ctx.nuxt.callHook('vite:serverCreated', viteServer, { isClient: true, isServer: false })
- const baseURL = joinURL(ctx.nuxt.options.app.baseURL.replace(/^\./, '') || '/', ctx.nuxt.options.app.buildAssetsDir)
- const BASE_RE = new RegExp(`^${escapeRE(withTrailingSlash(withLeadingSlash(baseURL)))}`)
const viteMiddleware: Connect.NextHandleFunction = (req, res, next) => {
// Workaround: vite devmiddleware modifies req.url
const originalURL = req.url!
- req.url = originalURL.replace(BASE_RE, '/')
+ if (!originalURL.startsWith('/__nuxt_vite_node__') && !originalURL.startsWith(clientConfig.base!)) {
+ req.url = joinURL('/__url', originalURL)
+ }
viteServer.middlewares.handle(req, res, (err: unknown) => {
req.url = originalURL
next(err)
diff --git a/packages/webpack/package.json b/packages/webpack/package.json
index c6b90b2a6ed..ba555e99f8d 100644
--- a/packages/webpack/package.json
+++ b/packages/webpack/package.json
@@ -31,7 +31,7 @@
"fs-extra": "^10.1.0",
"hash-sum": "^2.0.0",
"lodash-es": "^4.17.21",
- "magic-string": "^0.26.6",
+ "magic-string": "^0.26.7",
"memfs": "^3.4.7",
"mini-css-extract-plugin": "^2.6.1",
"mlly": "^0.5.16",
@@ -45,7 +45,7 @@
"style-resources-loader": "^1.5.0",
"time-fix-plugin": "^2.0.7",
"ufo": "^0.8.5",
- "unplugin": "^0.9.2",
+ "unplugin": "^0.9.6",
"url-loader": "^4.1.1",
"vue-bundle-renderer": "^0.4.3",
"vue-loader": "^17.0.0",
diff --git a/test/basic.test.ts b/test/basic.test.ts
index 2bbc001d444..cc964d76fce 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')
@@ -830,6 +835,10 @@ describe.skipIf(isWindows)('useAsyncData', () => {
await $fetch('/useAsyncData/refresh')
})
+ it('requests can be cancelled/overridden', async () => {
+ await expectNoClientErrors('/useAsyncData/override')
+ })
+
it('two requests made at once resolve and sync', async () => {
await expectNoClientErrors('/useAsyncData/promise-all')
})
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] }}