vuejs / router

🚦 The official router for Vue.js
https://router.vuejs.org/
MIT License
3.84k stars 1.18k forks source link

query params not parsed by vue router when they appear before the # #2054

Closed gureckis closed 9 months ago

gureckis commented 9 months ago

Reproduction

https://codesandbox.io/p/sandbox/vue-router-test-mk5s9m?file=%2Fsrc%2Frouter.js

Steps to reproduce the bug

Access the running app here. beforeEach on each route prints to the console the value of to.query.

When accessed this way the query parameters print out: https://mk5s9m.csb.app/#/?hi=1

when accessed this way the query parameters are empty. https://mk5s9m.csb.app/?hi=1#/

Expected behavior

The query parameters should be defined and print in both cases.

Actual behavior

The query parameters are only defined to the router when the appear after the '#' route selector

Additional information

Although it seems common to have the query parameters at the end of a URL in fact the query parameters are supposed to come before the hash tags. See section 3 of the RFC for the Uniform Resource Identifier spec: https://www.rfc-editor.org/rfc/rfc3986#section-3

I believe the solution is that the vue router needs to parse the query string even when they appear before the hash fragment.

posva commented 9 months ago

This is intended are both URL are different. When using the hash history, everything used by the router goes after the hash. Note that the hash isn't sent to server requests while a real query is

Zehir commented 3 months ago

@gureckis did you find any work around for that issue ? I have only one use case in my app where I don't build the URL so I endup like you.

My work around is to check if there is a token before the # and redirect the user to the correct route;

// In head of script setup in my password reset page
const queryToken = useRouteQuery<string>('token', '')
const router = useRouter()
const route = useRoute()

const currentUrl = new URL(window.location.href)
if (currentUrl.searchParams.has('token')) {
  queryToken.value = currentUrl.searchParams.get('token') as string
  const newUrl = router.resolve({
    path: route.path,
    query: { token: queryToken.value },
  })
  history.replaceState(
    history.state,
    '',
    window.location.origin + window.location.pathname + newUrl.href,
  )
}

Not pretty but only for password reset so won't be used a lot. I could just use the token with currentUrl.searchParams.get('token') but then the token persist on all page of the app so not great.

gureckis commented 3 months ago

yes, i added the following to my index.html in my vue/vite project:

 <body>
    <div id="app"></div>
    <script>
      ;(function () {
        const location = window.location
        let path = location.pathname
        let searchParams = new URLSearchParams(location.search)
        let hash = location.hash

        // Check if there is a hash and query parameters
        if (hash && location.search) {
          const hashParts = hash.split('?')
          const hashBase = hashParts[0]
          let hashParams = new URLSearchParams(hashParts[1])

          // Merge the parameters
          searchParams.forEach((value, key) => {
            hashParams.set(key, value)
          })

          // Create a new URL with merged parameters
          const newHash = `${hashBase}?${hashParams.toString()}`
          const newUrl = `${path}${newHash}`

          // Redirect to the new URL if it's different from the current one
          if (newUrl !== location.href) {
            window.location.replace(newUrl)
          }
        }
      })()
    </script>
    <script type="module" src="/src/core/main.js"></script>
  </body>

this runs quickly before main.js loads in most cases and immediately redirects the page to a new URL with the query parameters appearing after the hash parameters. I disagree with the perspective that the vue-router team has here but this worked for my case.