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

Server redirect implementation #144

Open
ruslansp opened this issue Oct 7, 2019 · 7 comments
Open

Server redirect implementation #144

ruslansp opened this issue Oct 7, 2019 · 7 comments

Comments

@ruslansp
Copy link

ruslansp commented Oct 7, 2019

I propose to add to the settings option for error handling on the server, for example, as handleError in Hackernews or add res in the server context
At this moment I did not find the possibility to implement a redirect on the server, for example, a 302 redirect to the login page.

@sneko
Copy link

sneko commented Mar 18, 2020

Hi @ruslansp , did you find how to do that?

Thank you,

@sneko
Copy link

sneko commented Mar 18, 2020

@Akryum maybe you have an idea on this? I saw multiple issues about that

Here is an example: https://stackoverflow.com/a/57925996/11314316 could be great to be able overriding all your error catching logic. We could like that custom if we send html or not depending on httpCode (applied only for redirections 301/302?)

Thank you,

@ruslansp
Copy link
Author

Hi. In version v0.6.0 added extendContext parameter in config. You can add respose to context and continue to use res for redirect. Not sure, but it seems you can solve it this way.
I’m not using it myself.

@sneko
Copy link

sneko commented Mar 19, 2020

Hi @ruslansp ,

If you look at

// Render the Vue app using the bundle renderer
const renderApp = (req, res) => {
res.setHeader('Content-Type', 'text/html')
const context = Object.assign({
req,
url: req.url,
title: config.defaultTitle,
httpCode: 200,
}, config.extendContext && config.extendContext(req, res, process))
renderer.renderToString(context, (err, renderedHtml) => {
let html = renderedHtml
if (err || context.httpCode === 500) {
console.error(`error during render url : ${req.url}`)
// Render Error Page
let errorHtml = config.error500Html
? fs.readFileSync(config.error500Html, 'utf-8')
: '500 | Internal Server Error'
if (err) {
console.error(err)
if (!isProd) {
const errorMessage = `<pre>${err.stack}</pre>`
config.error500Html
? errorHtml = errorHtml.replace('<!--server-error-msg-->', errorMessage)
: errorHtml += errorMessage
}
if (config.onError) {
config.onError(err)
}
}
html = errorHtml
res.status(500)
} else {
res.status(context.httpCode)
}
if (config.onRender) {
config.onRender(res, context)
}
res.send(html)
})
}
no matter context properties you set (like a status code of 301/302...) the code will always end with
res.send(html)

So even if I use context.req.res.redirect(302, '/xxxx'); somewhere, line 160 will try to send something... and it seems that's a problem since 302/301 HTTP response should only have minimal headers, no content...

Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
    at ServerResponse.setHeader (_http_outgoing.js:482:11)
    at ServerResponse.header (/Users/XXXXXXXXXX/Documents/YYYYYYY/node_modules/express/lib/response.js:767:10)
    at ServerResponse.send (/Users/XXXXXXXXXX/Documents/YYYYYYY/node_modules/express/lib/response.js:170:12)
    at renderer.renderToString (/Users/XXXXXXXXXX/Documents/YYYYYYY/node_modules/@akryum/vue-cli-plugin-ssr/lib/app.js:119:15)
    at /Users/XXXXXXXXXX/Documents/YYYYYYY/node_modules/vue-server-renderer/build.dev.js:9536:15
    at RenderContext.done (/Users/XXXXXXXXXX/Documents/YYYYYYY/node_modules/vue-server-renderer/build.dev.js:9187:15)
    at RenderContext.next (/Users/XXXXXXXXXX/Documents/YYYYYYY/node_modules/vue-server-renderer/build.dev.js:2588:19)
    at RenderContext.cachedWrite [as write] (/Users/XXXXXXXXXX/Documents/YYYYYYY/node_modules/vue-server-renderer/build.dev.js:2451:9)
    at RenderContext.next (/Users/XXXXXXXXXX/Documents/YYYYYYY/node_modules/vue-server-renderer/build.dev.js:2602:25)
    at RenderContext.cachedWrite [as write] (/Users/XXXXXXXXXX/Documents/YYYYYYY/node_modules/vue-server-renderer/build.dev.js:2451:9)
    at RenderContext.next (/Users/XXXXXXXXXX/Documents/YYYYYYY/node_modules/vue-server-renderer/build.dev.js:2602:25)
    at RenderContext.cachedWrite [as write] (/Users/XXXXXXXXXX/Documents/YYYYYYY/node_modules/vue-server-renderer/build.dev.js:2451:9)
    at RenderContext.next (/Users/XXXXXXXXXX/Documents/YYYYYYY/node_modules/vue-server-renderer/build.dev.js:2602:25)
    at RenderContext.cachedWrite [as write] (/Users/XXXXXXXXXX/Documents/YYYYYYY/node_modules/vue-server-renderer/build.dev.js:2451:9)
    at RenderContext.next (/Users/XXXXXXXXXX/Documents/YYYYYYY/node_modules/vue-server-renderer/build.dev.js:2602:25)
    at RenderContext.cachedWrite [as write] (/Users/XXXXXXXXXX/Documents/YYYYYYY/node_modules/vue-server-renderer/build.dev.js:2451:9)
(node:4202) UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
    at ServerResponse.setHeader (_http_outgoing.js:482:11)
    at ServerResponse.header (/Users/XXXXXXXXXX/Documents/YYYYYYY/node_modules/express/lib/response.js:767:10)
    at ServerResponse.send (/Users/XXXXXXXXXX/Documents/YYYYYYY/node_modules/express/lib/response.js:170:12)
    at renderer.renderToString (/Users/XXXXXXXXXX/Documents/YYYYYYY/node_modules/@akryum/vue-cli-plugin-ssr/lib/app.js:119:15)
    at /Users/XXXXXXXXXX/Documents/YYYYYYY/node_modules/vue-server-renderer/build.dev.js:9536:15
    at /Users/XXXXXXXXXX/Documents/YYYYYYY/node_modules/vue-server-renderer/build.dev.js:9170:20
(node:4202) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 2)

@almunnings
Copy link

almunnings commented Aug 28, 2020

Does #225 help with this?

It's allowed me to do something like:

In app set a variable like this.$root._routeRedirect = '/something'

entry-server.js

context.rendered = () => {
  // ...
  context._routeRedirect = app._routeRedirect || null
}

then in vue.config.js

For 404s I was just setting

extendContext = (req, res, process) => {
  const extension = {}
  if (req.path === '/404') {
    extension.httpCode = 404
  }
  return extension
}

For redirects based on a vue instance condition, changing the response's status late, to not match context.httpCode will stop res.send(html) from sending, stopping errors.

onRender = (res, context) => {
  if (context._routeRedirect) {
    res.redirect(301, context._routeRedirect)
  }
}

@sneko
Copy link

sneko commented Jun 10, 2022

2 years since then! And upgrading some Vue librairies makes the error Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client crashing the server now! Whereas before it was just some innocent logs... haha (that I had ignored for the ease)!

To keep my own logic of redirecting within components with this.$ssrContext.req.res.redirect(302, redirectUri) I just did a patch/PR #289 (@sonuku yours sounded a bit more complicated to me but I appreciate nonetheless!).

Hope this helps :)

@JeffJassky
Copy link

JeffJassky commented Oct 2, 2023

Here was my hacky solution that didn't require changing the main libs:

in vue.config.js:

module.exports = {
  pluginOptions: {
    ssr: {
      onRender(res, context){
        if(context._routeRedirect){
          res.redirect(301, context._routeRedirect); // do the redirect
          const send = res.send; // store local reference to original send method
          // override send method to prevent headers from being sent
          res.send = function(){
            res.send = send; // restore the original send method for subsequent calls
          }
        }
      },
      ... other configs
    }
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants