From cbf86c018bec5c0eda20e875fae278104a2e33d2 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 26 Apr 2022 22:44:20 +0100 Subject: [PATCH 1/6] fix!: process middleware after plugins --- packages/nuxt/src/app/plugins/router.ts | 57 ++++++++--------- packages/nuxt/src/pages/runtime/router.ts | 77 +++++++++++++---------- 2 files changed, 74 insertions(+), 60 deletions(-) diff --git a/packages/nuxt/src/app/plugins/router.ts b/packages/nuxt/src/app/plugins/router.ts index 3fcbc18d674..7d1f550f12f 100644 --- a/packages/nuxt/src/app/plugins/router.ts +++ b/packages/nuxt/src/app/plugins/router.ts @@ -86,6 +86,7 @@ interface Router { } export default defineNuxtPlugin<{ route: Route, router: Router }>((nuxtApp) => { + const initialURL = process.client ? window.location.href : nuxtApp.ssrContext.url const routes = [] const hooks: { [key in keyof RouterHooks]: RouterHooks[key][] } = { @@ -100,7 +101,7 @@ export default defineNuxtPlugin<{ route: Route, router: Router }>((nuxtApp) => { return () => hooks[hook].splice(hooks[hook].indexOf(guard), 1) } - const route: Route = reactive(getRouteFromPath(process.client ? window.location.href : nuxtApp.ssrContext.url)) + const route: Route = reactive(getRouteFromPath(initialURL)) async function handleNavigation (url: string, replace?: boolean): Promise { try { // Resolve route @@ -193,38 +194,38 @@ export default defineNuxtPlugin<{ route: Route, router: Router }>((nuxtApp) => { named: {} } - router.beforeEach(async (to, from) => { - to.meta = reactive(to.meta || {}) - nuxtApp._processingMiddleware = true - - const middlewareEntries = new Set(nuxtApp._middleware.global) - - for (const middleware of middlewareEntries) { - const result = await callWithNuxt(nuxtApp, middleware, [to, from]) - if (process.server) { - if (result === false || result instanceof Error) { - const error = result || createError({ - statusMessage: `Route navigation aborted: ${nuxtApp.ssrContext.url}` - }) - return callWithNuxt(nuxtApp, throwError, [error]) + nuxtApp.hooks.hookOnce('app:created', async () => { + router.beforeEach(async (to, from) => { + to.meta = reactive(to.meta || {}) + nuxtApp._processingMiddleware = true + + const middlewareEntries = new Set(nuxtApp._middleware.global) + + for (const middleware of middlewareEntries) { + const result = await callWithNuxt(nuxtApp, middleware, [to, from]) + if (process.server) { + if (result === false || result instanceof Error) { + const error = result || createError({ + statusMessage: `Route navigation aborted: ${initialURL}` + }) + return callWithNuxt(nuxtApp, throwError, [error]) + } } + if (result || result === false) { return result } } - if (result || result === false) { return result } - } - }) + }) - router.afterEach(() => { - delete nuxtApp._processingMiddleware - }) + router.afterEach(() => { + delete nuxtApp._processingMiddleware + }) - if (process.server) { - nuxtApp.hooks.hookOnce('app:created', async () => { - await router.push(nuxtApp.ssrContext.url) - if (route.fullPath !== nuxtApp.ssrContext.url) { - await navigateTo(route.fullPath) + if (process.server || !nuxtApp.payload.serverRendered) { + await router.replace(initialURL) + if (route.fullPath !== initialURL) { + await callWithNuxt(nuxtApp, navigateTo, [route.fullPath]) } - }) - } + } + }) return { provide: { diff --git a/packages/nuxt/src/pages/runtime/router.ts b/packages/nuxt/src/pages/runtime/router.ts index 9a7f90ad891..463c5d99cb1 100644 --- a/packages/nuxt/src/pages/runtime/router.ts +++ b/packages/nuxt/src/pages/runtime/router.ts @@ -48,7 +48,7 @@ function createCurrentLocation ( return path + search + hash } -export default defineNuxtPlugin((nuxtApp) => { +export default defineNuxtPlugin(async (nuxtApp) => { nuxtApp.vueApp.component('NuxtPage', NuxtPage) // TODO: remove before release - present for backwards compatibility & intentionally undocumented nuxtApp.vueApp.component('NuxtNestedPage', NuxtPage) @@ -59,6 +59,7 @@ export default defineNuxtPlugin((nuxtApp) => { ? createWebHistory(baseURL) : createMemoryHistory(baseURL) + const initialURL = process.server ? nuxtApp.ssrContext.url : createCurrentLocation(baseURL, window.location) const router = createRouter({ ...routerOptions, history: routerHistory, @@ -82,8 +83,7 @@ export default defineNuxtPlugin((nuxtApp) => { } // Allows suspending the route object until page navigation completes - const path = process.server ? nuxtApp.ssrContext.url : createCurrentLocation(baseURL, window.location) - const _activeRoute = shallowRef(router.resolve(path) as RouteLocation) + const _activeRoute = shallowRef(router.resolve(initialURL) as RouteLocation) const syncCurrentRoute = () => { _activeRoute.value = router.currentRoute.value } nuxtApp.hook('page:finish', syncCurrentRoute) router.afterEach((to, from) => { @@ -107,6 +107,28 @@ export default defineNuxtPlugin((nuxtApp) => { named: {} } + router.afterEach((to) => { + if (to.matched.length === 0) { + callWithNuxt(nuxtApp, throwError, [createError({ + statusCode: 404, + statusMessage: `Page not found: ${to.fullPath}` + })]) + } else if (process.server && to.matched[0].name === '404' && nuxtApp.ssrContext) { + nuxtApp.ssrContext.res.statusCode = 404 + } + }) + + try { + if (process.server) { + await router.push(initialURL) + } + + await router.isReady() + } catch (error) { + // We'll catch 404s here + callWithNuxt(nuxtApp, throwError, [error]) + } + router.beforeEach(async (to, from) => { to.meta = reactive(to.meta) nuxtApp._processingMiddleware = true @@ -141,7 +163,7 @@ export default defineNuxtPlugin((nuxtApp) => { if (process.server) { if (result === false || result instanceof Error) { const error = result || createError({ - statusMessage: `Route navigation aborted: ${nuxtApp.ssrContext.url}` + statusMessage: `Route navigation aborted: ${initialURL}` }) return callWithNuxt(nuxtApp, throwError, [error]) } @@ -150,40 +172,31 @@ export default defineNuxtPlugin((nuxtApp) => { } }) - router.afterEach(() => { + router.afterEach(async (to) => { delete nuxtApp._processingMiddleware - }) - - nuxtApp.hook('app:created', async () => { - router.afterEach((to) => { - if (to.matched.length === 0) { - callWithNuxt(nuxtApp, throwError, [createError({ - statusCode: 404, - statusMessage: `Page not found: ${to.fullPath}` - })]) - } else if (process.server && to.matched[0].name === '404' && nuxtApp.ssrContext) { - nuxtApp.ssrContext.res.statusCode = 404 - } - }) if (process.server) { - router.afterEach(async (to) => { - if (to.fullPath !== nuxtApp.ssrContext.url) { - await navigateTo(to.fullPath) - } - }) - } - - try { - if (process.server) { - await router.push(nuxtApp.ssrContext.url) + if (to.fullPath !== initialURL) { + await callWithNuxt(nuxtApp, navigateTo, [to.fullPath]) } - - await router.isReady() - } catch (error) { - callWithNuxt(nuxtApp, throwError, [error]) } }) + // On SSR, we trigger another navigation once all plugins are loaded + // On client-side, we do not process middleware on initial load + if (process.server || !nuxtApp.payload.serverRendered) { + nuxtApp.hooks.hookOnce('app:created', async () => { + try { + await router.replace({ + path: initialURL, + force: true + }) + } catch (error) { + // We'll catch middleware errors or deliberate exceptions here + callWithNuxt(nuxtApp, throwError, [error]) + } + }) + } + return { provide: { router } } }) From 613b83bcefb55f51394271607fa9b088e8b72562 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 26 Apr 2022 22:56:21 +0100 Subject: [PATCH 2/6] docs: add note about middleware not running on csr --- docs/content/2.guide/3.directory-structure/7.middleware.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/content/2.guide/3.directory-structure/7.middleware.md b/docs/content/2.guide/3.directory-structure/7.middleware.md index 0e78a9aefab..9db447d3a6d 100644 --- a/docs/content/2.guide/3.directory-structure/7.middleware.md +++ b/docs/content/2.guide/3.directory-structure/7.middleware.md @@ -20,6 +20,11 @@ There are three kinds of route middleware: The first two kinds of route middleware can be [defined in `definePageMeta`](/guide/directory-structure/pages). +::alert{type=info} +If the server has rendered a page, route middleware will not run again on the first load of that page, but will run on future client-side navigation. +:::StabilityEdge{title="Single-run route middleware"} +:: + ## Format Route middleware are navigation guards that receive the current route and the next route as arguments. From cc8d9193fb05ec1562a4cb817b627336975c958a Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Wed, 27 Apr 2022 10:33:28 +0100 Subject: [PATCH 3/6] docs: add closing tag for stabilityedge component --- docs/content/2.guide/3.directory-structure/7.middleware.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/content/2.guide/3.directory-structure/7.middleware.md b/docs/content/2.guide/3.directory-structure/7.middleware.md index 9db447d3a6d..e35623fa96d 100644 --- a/docs/content/2.guide/3.directory-structure/7.middleware.md +++ b/docs/content/2.guide/3.directory-structure/7.middleware.md @@ -23,6 +23,7 @@ The first two kinds of route middleware can be [defined in `definePageMeta`](/gu ::alert{type=info} If the server has rendered a page, route middleware will not run again on the first load of that page, but will run on future client-side navigation. :::StabilityEdge{title="Single-run route middleware"} +::: :: ## Format From 0287e6af3f3f286deae52997108fecc5bba5cb82 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Wed, 27 Apr 2022 21:44:53 +0100 Subject: [PATCH 4/6] test: add test for middleware running after plugin, and plugin having access to route --- test/fixtures/basic/middleware/redirect.global.ts | 5 +++++ test/fixtures/basic/plugins/my-plugin.ts | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/test/fixtures/basic/middleware/redirect.global.ts b/test/fixtures/basic/middleware/redirect.global.ts index 1c926d193ea..a3cd7a824a3 100644 --- a/test/fixtures/basic/middleware/redirect.global.ts +++ b/test/fixtures/basic/middleware/redirect.global.ts @@ -1,6 +1,11 @@ export default defineNuxtRouteMiddleware(async (to) => { + const nuxtApp = useNuxtApp() if (to.path.startsWith('/redirect/')) { await new Promise(resolve => setTimeout(resolve, 100)) return navigateTo(to.path.slice('/redirect/'.length - 1)) } + const pluginPath = nuxtApp.$path() + if (process.server && !/redirect|navigate/.test(pluginPath) && to.path !== pluginPath) { + throw new Error('plugin did not run before middleware') + } }) diff --git a/test/fixtures/basic/plugins/my-plugin.ts b/test/fixtures/basic/plugins/my-plugin.ts index 434af55e321..9f59aea0224 100644 --- a/test/fixtures/basic/plugins/my-plugin.ts +++ b/test/fixtures/basic/plugins/my-plugin.ts @@ -2,9 +2,11 @@ export default defineNuxtPlugin(() => { useHead({ titleTemplate: '%s - Fixture' }) + const path = useRoute().path return { provide: { - myPlugin: () => 'Injected by my-plugin' + myPlugin: () => 'Injected by my-plugin', + path: () => path } } }) From e14384e15698bd56768265d42038063d084f7171 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Fri, 29 Apr 2022 11:35:20 +0200 Subject: [PATCH 5/6] fix: run middleware on client-side initial load as well --- .../3.directory-structure/7.middleware.md | 6 ----- packages/nuxt/src/app/plugins/router.ts | 8 +++---- packages/nuxt/src/pages/runtime/router.ts | 24 +++++++++---------- 3 files changed, 14 insertions(+), 24 deletions(-) diff --git a/docs/content/2.guide/3.directory-structure/7.middleware.md b/docs/content/2.guide/3.directory-structure/7.middleware.md index e35623fa96d..0e78a9aefab 100644 --- a/docs/content/2.guide/3.directory-structure/7.middleware.md +++ b/docs/content/2.guide/3.directory-structure/7.middleware.md @@ -20,12 +20,6 @@ There are three kinds of route middleware: The first two kinds of route middleware can be [defined in `definePageMeta`](/guide/directory-structure/pages). -::alert{type=info} -If the server has rendered a page, route middleware will not run again on the first load of that page, but will run on future client-side navigation. -:::StabilityEdge{title="Single-run route middleware"} -::: -:: - ## Format Route middleware are navigation guards that receive the current route and the next route as arguments. diff --git a/packages/nuxt/src/app/plugins/router.ts b/packages/nuxt/src/app/plugins/router.ts index 7d1f550f12f..38656460413 100644 --- a/packages/nuxt/src/app/plugins/router.ts +++ b/packages/nuxt/src/app/plugins/router.ts @@ -219,11 +219,9 @@ export default defineNuxtPlugin<{ route: Route, router: Router }>((nuxtApp) => { delete nuxtApp._processingMiddleware }) - if (process.server || !nuxtApp.payload.serverRendered) { - await router.replace(initialURL) - if (route.fullPath !== initialURL) { - await callWithNuxt(nuxtApp, navigateTo, [route.fullPath]) - } + await router.replace(initialURL) + if (route.fullPath !== initialURL) { + await callWithNuxt(nuxtApp, navigateTo, [route.fullPath]) } }) diff --git a/packages/nuxt/src/pages/runtime/router.ts b/packages/nuxt/src/pages/runtime/router.ts index 463c5d99cb1..c92f911c3e7 100644 --- a/packages/nuxt/src/pages/runtime/router.ts +++ b/packages/nuxt/src/pages/runtime/router.ts @@ -184,19 +184,17 @@ export default defineNuxtPlugin(async (nuxtApp) => { // On SSR, we trigger another navigation once all plugins are loaded // On client-side, we do not process middleware on initial load - if (process.server || !nuxtApp.payload.serverRendered) { - nuxtApp.hooks.hookOnce('app:created', async () => { - try { - await router.replace({ - path: initialURL, - force: true - }) - } catch (error) { - // We'll catch middleware errors or deliberate exceptions here - callWithNuxt(nuxtApp, throwError, [error]) - } - }) - } + nuxtApp.hooks.hookOnce('app:created', async () => { + try { + await router.replace({ + path: initialURL, + force: true + }) + } catch (error) { + // We'll catch middleware errors or deliberate exceptions here + callWithNuxt(nuxtApp, throwError, [error]) + } + }) return { provide: { router } } }) From b3af100f593dae0f85bbc854441de8c30d22b359 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Mon, 2 May 2022 11:52:30 +0200 Subject: [PATCH 6/6] chore: update comment --- packages/nuxt/src/pages/runtime/router.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/nuxt/src/pages/runtime/router.ts b/packages/nuxt/src/pages/runtime/router.ts index c92f911c3e7..2da6ffbd696 100644 --- a/packages/nuxt/src/pages/runtime/router.ts +++ b/packages/nuxt/src/pages/runtime/router.ts @@ -182,8 +182,6 @@ export default defineNuxtPlugin(async (nuxtApp) => { } }) - // On SSR, we trigger another navigation once all plugins are loaded - // On client-side, we do not process middleware on initial load nuxtApp.hooks.hookOnce('app:created', async () => { try { await router.replace({