Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/bridge/src/runtime/app.plugin.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export default async (ctx, inject) => {
nuxtApp._processingMiddleware = true

for (const middleware of nuxtApp._middleware.global) {
const result = await middleware({ ...ctx, route: to, from })
const result = await middleware({ ...ctx, route: to, from, _isGlobalMiddleware: true })
if (result || result === false) { return next(result) }
}

Expand Down
20 changes: 19 additions & 1 deletion packages/bridge/src/runtime/composables/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export interface AddRouteMiddlewareOptions {
}

/** internal */
function convertToLegacyMiddleware (middleware) {
function convertToLegacyMiddleware (middleware: RouteMiddleware) {
return async (ctx: any) => {
// because the middleware is executed before the plugin
ctx.$_nuxtApp._processingMiddleware = true
Expand All @@ -64,6 +64,24 @@ function convertToLegacyMiddleware (middleware) {
if (result instanceof Error) {
return ctx.error(result)
}
if (result === false) {
// global middleware passes the result to `next` within `router.beforeEach`
if (ctx._isGlobalMiddleware) {
return result
}
// Nuxt 2 `middlewareSeries` stops calling the remaining middleware when `_redirected` is set
ctx._redirected = true
if (process.server && ctx.ssrContext['~renderResponse']) {
ctx.ssrContext.redirected = true
return
}
if (process.server || (!ctx.$_nuxtApp.payload.serverRendered && ctx.$_nuxtApp.isHydrating)) {
return ctx.error(createError({ statusCode: 404, statusMessage: `Page Not Found: ${ctx.route.fullPath}` }))
}
ctx.next(false)
ctx.$_nuxtApp.vue2App.$loading?.finish?.()
return
}
if (result) {
return ctx.redirect(result)
}
Expand Down
6 changes: 6 additions & 0 deletions playground/middleware/break-chain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default defineNuxtRouteMiddleware((to) => {
if ('abort' in to.query) {
return abortNavigation()
}
return navigateTo('/navigation-target')
})
3 changes: 3 additions & 0 deletions playground/middleware/unreachable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default defineNuxtRouteMiddleware(() => {
throw new Error('This middleware should not be called after the navigation has been redirected or aborted')
})
9 changes: 9 additions & 0 deletions playground/pages/middleware-chain/break.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<script setup lang="ts">
definePageMeta({
middleware: ['break-chain', 'unreachable']
})
</script>

<template>
<p>middleware-chain/break.vue</p>
</template>
22 changes: 22 additions & 0 deletions playground/pages/middleware-chain/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<script setup lang="ts">
const router = useRouter()
const aborted = ref(false)

function navigate () {
router.push('/middleware-chain/break?abort', undefined, () => {
aborted.value = true
})
}
</script>

<template>
<div>
<p>middleware-chain/index.vue</p>
<button @click="navigate">
navigate
</button>
<p v-if="aborted">
navigation aborted
</p>
</div>
</template>
25 changes: 25 additions & 0 deletions test/bridge.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,31 @@ describe('middleware', () => {
expect(html).toContain('Navigated successfully')
})

it('should not call the remaining middleware after navigateTo on server-side', async () => {
const res = await fetch('/middleware-chain/break', { redirect: 'manual' })

expect(res.status).toEqual(302)
expect(res.headers.get('location')).toEqual('/navigation-target')
})

it('should not call the remaining middleware after abortNavigation on server-side', async () => {
const res = await fetch('/middleware-chain/break?abort', { redirect: 'manual' })

expect(res.status).toEqual(404)
})

it('should abort navigation without calling the remaining middleware on client-side', async () => {
const { page, pageErrors } = await renderPage('/middleware-chain')
await page.getByRole('button', { name: 'navigate' }).click()
await page.getByText('navigation aborted').waitFor()

expect(new URL(page.url()).pathname).toEqual('/middleware-chain')
expect(await page.textContent('body')).toContain('middleware-chain/index.vue')
expect(pageErrors).toEqual([])

await page.close()
})

// if `no-resolve`, an error occurs at `useRoute`.
it.skipIf(isWebpack && isNoResolve)('should be output with target as to and origin as from', async () => {
const { page, consoleLogs } = await renderPage('/')
Expand Down
Loading