after.js: Redirecting from getInitialProps with res.redirect() causes error

When using res.redirect() in getInitialProps(), I get the following error:

(node:73586) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 4): Error: Can't set headers after they are sent.

The code is simply:

static async getInitialProps({ req, res, match, history, location, ...ctx }) {
    res.redirect('/');
}

The redirect does occur, but am I missing something due to the error?

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 16 (2 by maintainers)

Commits related to this issue

Most upvoted comments

You can also check if res.finished and if so return early. That’s how I ended up handling it. Sorry I didn’t put code above maybe would have saved you some time

 .get('/*', async (req, res) => {
    try {
      const html = await render({
        // ...
      });

      if (res.finished) return // if redirect, it'll happen on it's own
      res.send(html);
    } catch (error) {
      res.json(error);
    }

But anyway I’ve moved on from Afterjs because of basic things like this and decided to make my own solution 😃 will be releasing soon

Did you figure out a way to get it redirecting in the render method of the component?

The way to redirect in render by the way is:

import {Redirect} from 'react-router-dom'

class MyComponent extends React.Component {
  render() {
    return <Redirect to='/wherever' />
  }
}

Check the react-router docs if you need to know more.

🎉

Just got this to work without throwing an error.

problem 😫

As you noted, @alidcastano, the default behaviour of the .get route is to always return res.send(html). As noted elsewhere, if you call res.send or res.redirect multiple times in a row, then express is unhappy.

So, if we write

// server.js
 .get('/*', async (req, res) => {
    try {
      const html = await render({
        // ...
      });
     res.redirect('/');  // <-- it _will_ redirect but also throw an error
     res.send(html);
    } catch (error) {
      res.json(error);
    }

(ignoring that we have an infinite loop) then we get Error: Can't set headers after they are sent.

Instead, we have to explicitly return the response when we are done with it

// server.js
 .get('/*', async (req, res) => {
    try {
      const html = await render({
        // ...
      });
     return res.redirect('/');  // <-- now it works
     res.send(html);  // <-- this code is unreachable
    } catch (error) {
      res.json(error);

So that’s what we want.

When we’re in the getInitialProps for a component, we aren’t able to return the res to end the request. Writing return res.redirect('/') in getInitialProps will just take us to component’s constructor earlier. We’re still queuing up a res.redirect followed by a res.send once we’re back in the server. This is why we get the Error: we’re doing the same thing as in scenario 1.

solution 💡

We can do the redirect here if we figure out a way to pass a value back.

// server.js
 .get('/*', async (req, res) => {
    try {
      const html = await render({
        // ...
      });

     if(ifRedirect === true) {
       return res.redirect('/');
     }
     res.send(html);
    } catch (error) {
      res.json(error);
    }

So I was clicking around the express docs when I found res.locals. This is pretty much exactly what we want.

So now we can do an error-free server-side redirect:

// Page.js
class Page extends Component {
  static async getInitialProps({ res, req, match }) {
    res.locals.redirect = '/about'
  })
  // ...
}

// server.js
 .get('/*', async (req, res) => {
    try {
      const html = await render({
        // ...
      });

    return res.locals.redirect ? 
      res.redirect(res.locals.redirect)
      res.send(html);
    } catch (error) {
      res.json(error);
    }

Hope that helps you out! (Might also be useful for your other issue, #146 as well)

getInitialProps must be able to run on both client and server. You could check for res and all res.redirect but if it’s undefined call window.location.replace