Bexy-Lyn
Bexy-Lyn

Reputation: 157

Is it possible to redirect server-side with internationalization in Next.js?

So I have a small Next.js app, which has pages only allowed for logged in users. Nothing special there. To manage the authenticated routes I wrote an HOC (called withAuth) that redirects on the server, to avoid pages flashing on the client. It works great so far, but I have the issue, that my app is internationalized. So currently the user only gets redirected to the login in the default language (English), but I would prefer to keep their language selected.

Unfortunately, useRouter() is only available on the client, so my first approach did not succeed (I should have known better). This is my code:

auth.tsx

import redirect from "../utils/redirect";
import * as React from "react";
import { sendData } from "../utils/sendRequest";

export const withAuth = <T extends object>(C: React.ComponentClass<T>) => {
  return class AuthComponent extends React.Component<T> {
    static async getInitialProps({...ctx}: NextPageContext) {
      await sendData('GET','/user').then(response => {
        if (!response || !response.success) {
          redirect(ctx, "/login");
          return {
            user: null,
          };
        }else return {
          user: response.user,
        };
      }).catch(err=>{console.error(err)})

    }

    render() {
      return <C {...this.props} />;
    }
  };
};

redirect.ts

import Router, {useRouter} from "next/router";

const redirect = (context: any, target: string) => {
  let lang = "";
  /*if(!(useRouter.locale == "en")){
    lang = "/"+useRouter.locale; This wont work
  }*/
  if (context.res) {
    context.res.writeHead(303, { Location: /*lang+*/target });
    context.res.end();
  } else {
    Router.replace(/*lang+*/target);
  }
};
export default redirect;

profile.tsx

import * as React from "react";
import { withAuth } from "../components/auth";

//...

class ProfileClass extends React.PureComponent<{}> {
  render() {
    return <Profile />;
  }
}

export default withAuth(ProfileClass);

I am greatful for any ideas how to solve this problem :) Thanks!

Upvotes: 2

Views: 5782

Answers (1)

juliomalves
juliomalves

Reputation: 50278

Solution with getInitialProps in _app page.

You can access a router instance from the getInitialProps context object that can in turn access the current locale. Note that the router object is only available to the context passed to _app's getInitialProps - but not available to the context passed to the other pages.

// pages/_app.tsx

static async getInitialProps({ router, ctx }: NextPageContext) {
    const lang = router.locale;

    await sendData('GET','/user').then(response => {
        if (!response || !response.success) {
            redirect(ctx, `${lang}/login`);
            return {
                user: null,
            };
        } else {
            return {
                user: response.user,
            };
        }
    }).catch(err => { console.error(err) })
}

Solution with getServerSideProps for other pages.

Since locale information can't be accessed from a page with getInitialProps, you can use getServerSideProps instead. Doing so involves refactoring the existing logic to fit into getServerSideProps implementation.

First, we'll repurpose withAuth and separate the logic for getting the user. This will add the user object to the context and pass it to getServerSideProps.

// /components/auth

export function withAuth(gssp) {
    return async (context: GetServerSidePropsContext) => {
        try {
            const response = await sendData("GET", "/user");
            if (response && response.success) {
                // Add user to context to be used in `getServerSideProps`
                context = { ...context, user: response.user };
            }
        } catch (err) {
            console.error(err);
        }

        return await gssp(context); // Pass `context` and continue on to call `getServerSideProps`
    };
}

Then, withAuth can be used to wrap the getServerSideProps function in your page, where the redirect will happen if the user isn't set.

import * as React from "react";
import { withAuth } from "../components/auth";

export const getServerSideProps: GetServerSideProps = withAuth(async (context: : GetServerSidePropsContext) => {
    if (!context.user) {
        return {
            redirect: {
                destination: `/${context.locale}/login`,
                statusCode: 303
            }
        };
    }

    return {
        props: { 
            user: context.user 
        }
    };
});

class ProfileClass extends React.PureComponent<{}> {
    render() {
        return <Profile />;
    }
}

export default ProfileClass;

Upvotes: 2

Related Questions