jaredpalmer / after.js

Next.js-like framework for server-rendered React apps built with React Router
https://npm.im/@jaredpalmer/after
MIT License
4.13k stars 201 forks source link

redirectTo not working, Throwing Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client #529

Open qualwebs opened 2 years ago

qualwebs commented 2 years ago

🐛 Bug report

The issue with redirection inside getInitialProps method

Current Behavior

I'm using AfterJS with razzle, Using a class component, and trying to validate the authorization inside getInitialProps method as suggested in the example of afterJs GitHub readme.

Here is the code sample

class Home extends React.Component {
    static async getInitialProps({req, res, match, history, location, ...ctx}) {
        try {
            firstSection = await api.authME('TOKEN');
        }catch (error) {
            if (error.response.status === 401) {
                return { redirectTo: '/login' };
            }
            return { error };
        }
    }
}

Expected behavior

It should redirect to login page if API returns 401 status code, Instead of throwing error Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client

Reproducible example

Suggested solution(s)

Additional context

Your environment

LOCAL

Software Version(s)
React ^17.0.2
After.js ^latest
Razzle ^4.2.6
Razzle Plugins
TypeScript N/A
Node 16.14.0
Browser CHROME
npm/Yarn NPM
Operating System WINDOWS
qualwebs commented 2 years ago

@jaredpalmer If someone can look into please :)

blinkcat commented 2 years ago

@qualwebs This is a bug indeed. if your code return { redirectTo: '/login' }, res.redirectwill be called before res.send. meanwhile, the html variable will be empty string, so we can try this:

// your server.js in src
server
  .disable('x-powered-by')
  .use(express.static(process.env.RAZZLE_PUBLIC_DIR))
  .get('/*', async (req, res) => {
    try {
      const html = await render({
        req,
        res,
        routes,
        assets,
        chunks,
      });
       if(html){ // add a judgement here
         res.send(html);
      }
    } catch (error) {
      console.error(error);
      res.json({ message: error.message, stack: error.stack });
    }
  });
jschroed91 commented 2 years ago

We ended up using a very similar solution to what @blinkcat posted here.

But instead of if(html){ we did

if (res.statusCode == '302' || res.statusCode == '301') {
  return;
}

res.send(html);

I'm using == there instead of strict comparison === because I don't recall what type res.statusCode was , but obvi a strict comparison would be better. But you get the point

I think in our case html was still getting a value, so that's why we went this route.

stale[bot] commented 2 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.