Using next-i18next & NextAuth.js in the same NextJS app
Tags: nextjs, typescript, tutorial, programming
Published: 2022-04-17 16:05:48

This is my first tutorial, the intention of this one is to make life easier and save time for those who need to implement these technologies in the same project, it took me a while to implement it and I want to share it with whoever may need it.
I hope you find it useful...

## 1. We create a new project as indicated [in the documentation](https://nextjs.org/docs/getting-started).
I'm going to do it with Typescript but with Javascript the steps are the same.
```bash
npx create-next-app@latest --typescript
```
You type the name of your project and wait for the dependencies to be installed, then you run VSCode or your code editor in the project folder.
Now in the terminal type.
```bash
npm run dev
```
This will start the development server on port 3000.

![Initial App](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ymqtjzsu5bymqhm3yvxs.png)

## 2. Install and configure [next-i18next](https://github.com/isaachinman/next-i18next)
As indicated in the project repository we run
```bash
yarn add next-i18next 
# Si usas NPM ejecuta:
npm i --save next-i18next
```

Now we create two folders with json files to test the translations:
```
└─ public
   └ locales
     ├─en
     | └─ common.json
     └─es
       └─ common.json
```
Then we create a `next-i18next.config.js` file in the root of the project with the following content:
```js
module.exports = {
  i18n: {
    defaultLocale: 'en',
    locales: ['en', 'es'],
  },
};
```
Then in the `next.config.js` file we import the configuration:
```js
/** @type {import('next').NextConfig} */
const { i18n } = require("./next-i18next.config");
const nextConfig = {
  reactStrictMode: true,
  i18n,
};

module.exports = nextConfig;
```
Finally we edit the `pages/_app.tsx` file to run the application with translations, the change is made in the last line.

```ts
import '../styles/globals.css';
import type { AppProps } from 'next/app';
import { appWithTranslation } from 'next-i18next';

function MyApp({ Component, pageProps }: AppProps) {
  return <Component {...pageProps} />;
}

export default appWithTranslation(MyApp);
```

## 3. Add and test translations.
In the files `public/locales/en/common.json` and `public/locales/en/common.json` we add the translations in this way:
`public/locales/en/common.json`
```json
{
  "welcome": "Welcome to the world of Next.js!",
  "content": "This is the blog index page"
}
```

`public/locales/en/common.json`
```json
{
  "welcome": "Bienvenido al mundo de Next.js!",
  "content": "Esta es la página de inicio del blog"
}
```

Now, this step is important, for the translations to work correctly, they must be called from the server in all the components at page level for it to perform the "hydration" correctly, in this case we edit our `pages/index.tsx` file to pass the translations, so we can call each translation individually we use

```ts
const { t } = useTranslation('common');
```

The file in this case looks like this:

```tsx
import type { NextPage, NextPageContext } from 'next';
import Head from 'next/head';
import Image from 'next/image';
import styles from '../styles/Home.module.css';

const Home: NextPage = () => {
  const { t } = useTranslation('common');
  return (
    <div className={styles.container}>
      <Head>
        <title>Create Next App</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main className={styles.main}>
        <h1 className={styles.title}>{t('welcome')}</h1>

        <p className={styles.description}>{t('content')}</p>
      </main>

      <footer className={styles.footer}>
        <a
          href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
          target="_blank"
          rel="noopener noreferrer"
        >
          Powered by{' '}
          <span className={styles.logo}>
            <Image src="/vercel.svg" alt="Vercel Logo" width={72} height={16} />
          </span>
        </a>
      </footer>
    </div>
  );
};

export default Home;
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import { useTranslation } from 'next-i18next';

export async function getStaticProps({ locale }: NextPageContext) {
  return {
    props: {
      ...(await serverSideTranslations(locale || 'en', ['common'])),
    },
  };
}
```

The result is the translated page with the language detected in the system.
![Translated Page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/foq27bs3fp2njh7i0i2a.png)

If we want to manually test the language change we have to change the configuration, I will also add another line that will serve to upload our project to [Vercel](https://vercel.com/), the file looks like this:

```js
const path = require('path');

module.exports = {
  i18n: {
    defaultLocale: 'en',
    locales: ['en', 'es'],
    localeDetection: false,
    localePath: path.resolve('./public/locales'), // for deployment on Vercel
  },
};
```
We will also create a button to change the language manually, the `pages/index.tsx` file would look like this:

```tsx
import type { NextPage, NextPageContext } from 'next';
import Head from 'next/head';
import Image from 'next/image';
import styles from '../styles/Home.module.css';
import { useRouter } from 'next/router';

const Home: NextPage = () => {
  const router = useRouter();
  const { pathname, asPath, query } = router;
  const { t } = useTranslation('common');
  return (
    <div className={styles.container}>
      <Head>
        <title>Create Next App</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main className={styles.main}>
        <h1 className={styles.title}>{t('welcome')}</h1>

        {/* Language Change Button */}
        <button
          type="button"
          onClick={() => {
            router.push({ pathname, query }, asPath, {
              locale: router.locale === 'es' ? 'en' : 'es',
            });
          }}
        >
          {router.locale === 'es' ? 'English' : 'Español'}
        </button>

        <p className={styles.description}>{t('content')}</p>
      </main>

      <footer className={styles.footer}>
        <a
          href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
          target="_blank"
          rel="noopener noreferrer"
        >
          Powered by{' '}
          <span className={styles.logo}>
            <Image src="/vercel.svg" alt="Vercel Logo" width={72} height={16} />
          </span>
        </a>
      </footer>
    </div>
  );
};

export default Home;
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import { useTranslation } from 'next-i18next';

export async function getStaticProps({ locale }: NextPageContext) {
  return {
    props: {
      ...(await serverSideTranslations(locale || 'en', ['common'])),
    },
  };
}
```

In this way we already have our application working perfectly with two languages based on the route


## 4. We configure authentication with [next-auth](https://next-auth.js.org)

First we install the package:
```bash
npm i --save next-auth
```

For testing purposes we will use credentials (email and password), also we will do a simple validation, doing it correctly depends on what you want in your project, if you comment I can do a tutorial explaining how to do the authentication with different providers and using a customizable home page. That said we continue...

We create a `pages/api/auth/[...nextauth].ts` file.
With the following content:

```ts
import NextAuth from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';

export default NextAuth({
  providers: [
    CredentialsProvider({
      name: 'credentials',
      credentials: {
        email: {
          label: 'Email',
          type: 'email',
          placeholder: 'email@example.com',
        },
        password: {
          label: 'Password',
          type: 'password',
          placeholder: '********',
        },
      },
      async authorize(credentials) {
        if (credentials && credentials.email && credentials.password) {
          if (
            credentials.email === 'test@test.com' &&
            credentials.password === 'test'
          ) {
            return {
              email: credentials.email,
              image: 'https://i.pravatar.cc/500',
              name: 'Test User',
            };
          }
        }
        return null;
      },
    }),
  ],
  callbacks: {
    jwt: async ({ token }) => {
      return token;
    },
    session: ({ session, token }) => {
      if (token) {
        session.id = token.id;
      }
      return session;
    },
  },
  secret: 'my-secret',
  jwt: {
    secret: 'my-secret',
    maxAge: 60 * 60 * 24 * 30,
  },
});
```

Then we add the `pages/index.tsx` file to have an authentication or logout button in addition to the language change.

```tsx
import type { NextPage, NextPageContext } from 'next';
import Head from 'next/head';
import Image from 'next/image';
import styles from '../styles/Home.module.css';
import { useRouter } from 'next/router';
import { useSession, signIn, signOut } from 'next-auth/react';

const Home: NextPage = () => {
  const router = useRouter();
  const { pathname, asPath, query } = router;
  const { t } = useTranslation('common');
  const { data: session } = useSession();

  return (
    <div className={styles.container}>
      <Head>
        <title>Create Next App</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main className={styles.main}>
        <h1 className={styles.title}>{t('welcome')}</h1>

        {/* Language Change Button */}
        <button
          type="button"
          onClick={() => {
            router.push({ pathname, query }, asPath, {
              locale: router.locale === 'es' ? 'en' : 'es',
            });
          }}
        >
          {router.locale === 'es' ? 'English' : 'Español'}
        </button>

        {/* Authentication Button */}
        {session ? (
          <>
            <h4>
              {t('welcome')} {JSON.stringify(session.user)}
            </h4>
            <button
              type="button"
              onClick={() => {
                signOut();
              }}
            >
              {t('signOut')}
            </button>
          </>
        ) : (
          <button
            type="button"
            onClick={() => {
              signIn();
            }}
          >
            {t('signIn')}
          </button>
        )}

        <p className={styles.description}>{t('content')}</p>
      </main>

      <footer className={styles.footer}>
        <a
          href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
          target="_blank"
          rel="noopener noreferrer"
        >
          Powered by{' '}
          <span className={styles.logo}>
            <Image src="/vercel.svg" alt="Vercel Logo" width={72} height={16} />
          </span>
        </a>
      </footer>
    </div>
  );
};

export default Home;
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import { useTranslation } from 'next-i18next';

export async function getStaticProps({ locale }: NextPageContext) {
  return {
    props: {
      ...(await serverSideTranslations(locale || 'en', ['common'])),
    },
  };
}
```

Now the most important thing, probably the reason why you come to this post, and personally what I found most difficult to solve, just make a small change in the `pages/_app.tsx` file so that the SessionProvider does not collide with the Translation Provider:

```tsx
import '../styles/globals.css';
import type { AppProps } from 'next/app';
import { appWithTranslation } from 'next-i18next';
import { SessionProvider } from 'next-auth/react';

function MyApp({ Component, pageProps }: AppProps) {
  return <Component {...pageProps} />;
}

const AppWithI18n = appWithTranslation(MyApp);

const AppWithAuth = (props: AppProps) => (
  <SessionProvider session={props.pageProps.session}>
    <AppWithI18n {...props} />
  </SessionProvider>
);

export default AppWithAuth;
```

And that's it, now we can change the language of our page and have the authentication in the same NextJS application.

![Auth in English](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8ep20jnsu50annoq7o8o.png)

![Auth in Spanish](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pt2nnsgeb68lq7gws418.png)


I hope I helped you, if you have any comments or questions leave them in the comments to help more developers.