diff --git a/packages/nuxt/src/app/plugins/router.ts b/packages/nuxt/src/app/plugins/router.ts index 3fcbc18d67..3865646041 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,36 @@ export default defineNuxtPlugin<{ route: Route, router: Router }>((nuxtApp) => { named: {} } - router.beforeEach(async (to, from) => { - to.meta = reactive(to.meta || {}) - nuxtApp._processingMiddleware = true + 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) + 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]) + 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 } - } - }) - - 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 (result || result === false) { return result } } }) - } + + router.afterEach(() => { + delete nuxtApp._processingMiddleware + }) + + 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 9a7f90ad89..2da6ffbd69 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,37 +172,24 @@ 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() + 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]) } }) diff --git a/test/fixtures/basic/middleware/redirect.global.ts b/test/fixtures/basic/middleware/redirect.global.ts index 1c926d193e..a3cd7a824a 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 434af55e32..9f59aea022 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 } } })