Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ability to set and reference redirectParams #2582

Closed
trainiac opened this issue Jan 16, 2019 · 15 comments
Closed

Ability to set and reference redirectParams #2582

trainiac opened this issue Jan 16, 2019 · 15 comments
Labels
fixed on 4.x This issue has been already fixed on the v4 but exists in v3
Projects

Comments

@trainiac
Copy link
Contributor

What problem does this feature solve?

In order to issue a 301 redirect vs a 302 redirect in vue router there is no easy way to do that in userland. Currently you have to write a custom redirect function and throw an error with the information that you need.

{ 
  path: '/old-path': 
  redirect() => {
    const error = new Error('Permanent Redirect')
    error.redirect = {
      code: 301,
      url: '/new-path',
    }
    throw error
  }
}

What does the proposed API look like?

In your redirects you can define

{ 
  path: '/old-path': 
  redirect: '/new-path', 
  redirectParams: { 
    responseCode: 301 
  } 
}

And in a beforeEach handler you could reference the meta info

router.beforeEach((to, from, next) => {
   to.redirectedFrom === '/old-path'
   to.redirectParams.responseCode === 301
})
@posva
Copy link
Member

posva commented Jan 17, 2019

I'm not sure I'm getting it here. This is related to SSR, right? otherwise it wouldn't serve any purpose 🤔

@trainiac
Copy link
Contributor Author

Yes this is intended for SSR.

@trainiac
Copy link
Contributor Author

Most of the time this can be handled before your vue application gets called server side, however it's nice to take advantage of vue-router's route matching capabilities and sometimes you need to make some API calls before knowing where to redirect the user.

@trainiac
Copy link
Contributor Author

trainiac commented Jan 17, 2019

Here is a simplified example of what I'm trying to do.

Routes

[
    { 
      path: '/foo': 
      redirect: '/foo-new', 
      redirectParams: { 
        responseCode: 301 
      } 
    },
    { 
      path: '/bar': 
      redirect: '/bar-new', 
      redirectParams: { 
        responseCode: 302 
      } 
    },
    { 
      path: '/foo-new', 
      component: FooNew, 
    },
    { 
      path: '/bar-new', 
      component: BarNew 
    },
    {
      path: '*',
      component: NotFound,
      meta: {
        notFound: true,
      },
    },
  ]

Server side request handler

module.exports = (req, res, next) => {

  renderer.renderToString(context, (err, html) => {
   
    if (err) {
      if (err.redirectUrl) {
        const responseCode = err.responseCode || HttpStatus.MOVED_TEMPORARILY
        res.redirect(responseCode, err.redirectUrl)
      } else if (err.notFound) {
        res.status(HttpStatus.NOT_FOUND).send('404')
      } else {
        next(err)
      }
      return
    }
    res.send(html)
  })
}

Server entry point

export default context => {
  const router = createRouter()
  const app = createApp(router)

  return new Promise((resolve, reject) => {
    router.onError(reject)
    router.onReady(resolve, reject)
    router.push(context.url)
  })
    .then(() => {
      const to = router.currentRoute
      if (to.meta.notFound) {
        const error = new Error('Route not found')
        error.notFound = true
        return Promise.reject(error)
      }
      
      if (to.redirectedFrom) {
        const error = new Error('Redirect')
        error.redirectUrl = to.fullPath
        error.responseCode = to.redirectParams.responseCode
        return Promise.reject(error)
      } 
      return app
    })
}

@trainiac
Copy link
Contributor Author

trainiac commented Jan 18, 2019

In cases where the route that you want to redirect to exists you could do

[
    { 
      path: '/foo': 
      redirect: {
         name: 'fooNew', 
         params: { 
            responseCode: 301 
          } 
       }
    },
    { 
      path: '/bar': 
      redirect: {
        name: 'barNew',
        params: {
          responseCode: 302
        },
      }
    },
    { 
      name: 'fooNew',
      path: '/foo-new', 
      component: FooNew, 
    },
    { 
      name: 'barNew',
      path: '/bar-new', 
      component: BarNew 
    },
  ]

But when the redirect is to a path outside of your application you don't get access to any sort of data like params because it ends up redirecting to the Not Found component.

@posva
Copy link
Member

posva commented Jan 19, 2019

hey @Atinux, do you think there is something we could work on together to make redirections? I think we should add some way of detecting what kind of navigation was triggered by the router. Keep some trace of a navigation being a redirect (#1822) and the kind of navigation (push/replace #1620) and the route the navigation came from #883

@posva posva added this to To consider (do not agree with reasoning or could be exposed as plugin) in Longterm Mar 26, 2019
@posva posva added fixed on 4.x This issue has been already fixed on the v4 but exists in v3 and removed fixed on 4.x This issue has been already fixed on the v4 but exists in v3 labels Apr 20, 2020
@posva
Copy link
Member

posva commented Apr 22, 2020

I understood better what you wanted after your reply on the RFC. This can be achieved with a meta property on the redirect record on v4 because redirectedFrom is a normalized location instead of a raw one so we can read its meta properties

@posva posva added the fixed on 4.x This issue has been already fixed on the v4 but exists in v3 label Apr 22, 2020
@trainiac
Copy link
Contributor Author

trainiac commented Apr 22, 2020

@posva That definitely solves this issue, is a good idea, and closes the issue!

However there are cases when calling next('/url-to-redirect-to') where you also want to make a similar decision of what response code to redirect with. This is a separate issue and maybe not worth solving.

Let's say we are migrating to a new url /person/1234 -> /users/posva. In otherwords /person/:someNumberId -> /users/:someStringId because we want to start using strings for ids instead of numbers. In order to know what the redirect url is we need to be able make an api call to resolve old id with the new one. This means the decision to redirect will likely be somewhere in the route/component life cycle where your option for redirection is next().

I've been able to achieve this by throwing errors with response codes on them and it all actually works fine in the RFC you've proposed. I think the only thing I'm looking for is the ability to optionally pass an http status code to next() or somehow pass some info along to the beforeEach handler (like the NavigationResult) that includes this information.

I suppose you could do next({ name: 'newRoute', params: { httpStatusCode: 301 } })

@posva
Copy link
Member

posva commented Apr 22, 2020

For that scenario, you should still be able to use meta by creating two different routes

/person/:id(\d+)
/person/:id

Then, in the code checking for redirectedFrom, you would be able to get the meta of the first route.

Passing params that do not exist on the path is not supported (even though it works in some scenarios)

@trainiac
Copy link
Contributor Author

You're solution to this implies that you decide up front in the route meta configuration the redirect repsonse code that it always issues. What if you wanted in some cases to issue a 301 redirect from a route and in other cases a 302 redirect from that same route.

For example, a 302 if the user isn't authenticated and a 301 redirect to the new url if they are. You could say I'll let the new url handle the auth code and always 301 from the old url but I'm sure there are examples where a developer might want to have the fine grained control to say for this route in this case do a 301 and in another case do a 302. That decison feels like it should come from within the application logic and not the route configuration.

@posva
Copy link
Member

posva commented Apr 23, 2020

For those cases, the value should be saved in some global state like the store so the status code is retrievable before responding to the request

@trainiac
Copy link
Contributor Author

@posva that is an interesting idea. I think another similar solution would be to update the serverContext object. Thank you!

@posva
Copy link
Member

posva commented Apr 23, 2020

I'm not sure what you are referring by serverContext but it's probably a piece of global state that is SSR only. As long as it is unique per request, then it's good

@trainiac
Copy link
Contributor Author

trainiac commented Apr 23, 2020

Correct! Sorry for the confusion I was referring to the context object that you pass into an server entry point. https://ssr.vuejs.org/guide/structure.html#entry-server-js.

A new one gets created per request and it can be mutated over the course of a request with response headers and other server specific info needed for responding. Seems like the perfect thing to set a responseCode on.

@posva
Copy link
Member

posva commented Apr 23, 2020

Yes, that's perfect. I'm closing this but we will have to document this specific part about using the server context to setup redirections

@posva posva closed this as completed Apr 23, 2020
@posva posva moved this from To consider (do not agree with reasoning or could be exposed as plugin, needs more discussing) to Long term road (high prio, low complex) in Longterm May 9, 2020
@posva posva moved this from Long term road (high prio, low complex) to Done in Longterm May 9, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
fixed on 4.x This issue has been already fixed on the v4 but exists in v3
Projects
Development

No branches or pull requests

2 participants