mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-22 13:45:18 +00:00
feat(nuxt): navigateTo
supports external redirects (#5022)
Co-authored-by: Pooya Parsa <pooya@pi0.io>
This commit is contained in:
parent
856c2a6fbd
commit
a4dfe232f0
@ -1,7 +1,7 @@
|
|||||||
import { getCurrentInstance, inject } from 'vue'
|
import { getCurrentInstance, inject } from 'vue'
|
||||||
import type { Router, RouteLocationNormalizedLoaded, NavigationGuard, RouteLocationNormalized, RouteLocationRaw, NavigationFailure } from 'vue-router'
|
import type { Router, RouteLocationNormalizedLoaded, NavigationGuard, RouteLocationNormalized, RouteLocationRaw, NavigationFailure, RouteLocationPathRaw } from 'vue-router'
|
||||||
import { sendRedirect } from 'h3'
|
import { sendRedirect } from 'h3'
|
||||||
import { joinURL } from 'ufo'
|
import { hasProtocol, joinURL, parseURL } from 'ufo'
|
||||||
import { useNuxtApp, useRuntimeConfig } from '#app'
|
import { useNuxtApp, useRuntimeConfig } from '#app'
|
||||||
|
|
||||||
export const useRouter = () => {
|
export const useRouter = () => {
|
||||||
@ -58,26 +58,49 @@ const isProcessingMiddleware = () => {
|
|||||||
|
|
||||||
export interface NavigateToOptions {
|
export interface NavigateToOptions {
|
||||||
replace?: boolean
|
replace?: boolean
|
||||||
redirectCode?: number
|
redirectCode?: number,
|
||||||
|
external?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const navigateTo = (to: RouteLocationRaw | undefined | null, options: NavigateToOptions = {}): Promise<void | NavigationFailure> | RouteLocationRaw => {
|
export const navigateTo = (to: RouteLocationRaw | undefined | null, options: NavigateToOptions = {}): Promise<void | NavigationFailure> | RouteLocationRaw => {
|
||||||
if (!to) {
|
if (!to) {
|
||||||
to = '/'
|
to = '/'
|
||||||
}
|
}
|
||||||
// Early redirect on client-side since only possible option is redirectCode and not applied
|
|
||||||
if (process.client && isProcessingMiddleware()) {
|
const toPath = typeof to === 'string' ? to : ((to as RouteLocationPathRaw).path || '/')
|
||||||
|
const isExternal = hasProtocol(toPath, true)
|
||||||
|
if (isExternal && !options.external) {
|
||||||
|
throw new Error('Navigating to external URL is not allowed by default. Use `nagivateTo (url, { external: true })`.')
|
||||||
|
}
|
||||||
|
if (isExternal && parseURL(toPath).protocol === 'script:') {
|
||||||
|
throw new Error('Cannot navigate to an URL with script protocol.')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Early redirect on client-side
|
||||||
|
if (!isExternal && isProcessingMiddleware()) {
|
||||||
return to
|
return to
|
||||||
}
|
}
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
if (process.server) {
|
if (process.server) {
|
||||||
const nuxtApp = useNuxtApp()
|
const nuxtApp = useNuxtApp()
|
||||||
if (nuxtApp.ssrContext && nuxtApp.ssrContext.event) {
|
if (nuxtApp.ssrContext && nuxtApp.ssrContext.event) {
|
||||||
const redirectLocation = joinURL(useRuntimeConfig().app.baseURL, router.resolve(to).fullPath || '/')
|
const redirectLocation = isExternal ? toPath : joinURL(useRuntimeConfig().app.baseURL, router.resolve(to).fullPath || '/')
|
||||||
return nuxtApp.callHook('app:redirected').then(() => sendRedirect(nuxtApp.ssrContext!.event, redirectLocation, options.redirectCode || 302))
|
return nuxtApp.callHook('app:redirected').then(() => sendRedirect(nuxtApp.ssrContext!.event, redirectLocation, options.redirectCode || 301))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client-side redirection using vue-router
|
// Client-side redirection using vue-router
|
||||||
|
if (isExternal) {
|
||||||
|
if (options.replace) {
|
||||||
|
location.replace(toPath)
|
||||||
|
} else {
|
||||||
|
location.href = toPath
|
||||||
|
}
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
|
||||||
return options.replace ? router.replace(to) : router.push(to)
|
return options.replace ? router.replace(to) : router.push(to)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,6 +179,14 @@ describe('navigate', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('navigate external', () => {
|
||||||
|
it('should redirect to example.com', async () => {
|
||||||
|
const { headers } = await fetch('/navigate-to-external/', { redirect: 'manual' })
|
||||||
|
|
||||||
|
expect(headers.get('location')).toEqual('https://example.com/')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('errors', () => {
|
describe('errors', () => {
|
||||||
it('should render a JSON error page', async () => {
|
it('should render a JSON error page', async () => {
|
||||||
const res = await fetch('/error', {
|
const res = await fetch('/error', {
|
||||||
|
7
test/fixtures/basic/pages/navigate-to-external.vue
vendored
Normal file
7
test/fixtures/basic/pages/navigate-to-external.vue
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<template>
|
||||||
|
<div>You should not see me</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
await navigateTo('https://example.com/', { external: true, replace: true })
|
||||||
|
</script>
|
Loading…
Reference in New Issue
Block a user