DEV Community

Cover image for Using next-i18next & NextAuth.js in the same NextJS app
Ángel Quiroz
Ángel Quiroz

Posted on • Updated on

Using next-i18next & NextAuth.js in the same NextJS app

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.

I'm going to do it with Typescript but with Javascript the steps are the same.

npx create-next-app@latest --typescript
Enter fullscreen mode Exit fullscreen mode

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.

npm run dev
Enter fullscreen mode Exit fullscreen mode

This will start the development server on port 3000.

Initial App

2. Install and configure next-i18next

As indicated in the project repository we run

yarn add next-i18next 
# Si usas NPM ejecuta:
npm i --save next-i18next
Enter fullscreen mode Exit fullscreen mode

Now we create two folders with json files to test the translations:

└─ public
   └ locales
     ├─en
     | └─ common.json
     └─es
       └─ common.json
Enter fullscreen mode Exit fullscreen mode

Then we create a next-i18next.config.js file in the root of the project with the following content:

module.exports = {
  i18n: {
    defaultLocale: 'en',
    locales: ['en', 'es'],
  },
};
Enter fullscreen mode Exit fullscreen mode

Then in the next.config.js file we import the configuration:

/** @type {import('next').NextConfig} */
const { i18n } = require("./next-i18next.config");
const nextConfig = {
  reactStrictMode: true,
  i18n,
};

module.exports = nextConfig;
Enter fullscreen mode Exit fullscreen mode

Finally we edit the pages/_app.tsx file to run the application with translations, the change is made in the last line.

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);
Enter fullscreen mode Exit fullscreen mode

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

{
  "welcome": "Welcome to the world of Next.js!",
  "content": "This is the blog index page"
}
Enter fullscreen mode Exit fullscreen mode

public/locales/en/common.json

{
  "welcome": "Bienvenido al mundo de Next.js!",
  "content": "Esta es la página de inicio del blog"
}
Enter fullscreen mode Exit fullscreen mode

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

const { t } = useTranslation('common');
Enter fullscreen mode Exit fullscreen mode

The file in this case looks like this:

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'])),
    },
  };
}
Enter fullscreen mode Exit fullscreen mode

The result is the translated page with the language detected in the system.
Translated Page

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, the file looks like this:

const path = require('path');

module.exports = {
  i18n: {
    defaultLocale: 'en',
    locales: ['en', 'es'],
    localeDetection: false,
    localePath: path.resolve('./public/locales'), // for deployment on Vercel
  },
};
Enter fullscreen mode Exit fullscreen mode

We will also create a button to change the language manually, the pages/index.tsx file would look like this:

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'])),
    },
  };
}
Enter fullscreen mode Exit fullscreen mode

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

4. We configure authentication with next-auth

First we install the package:

npm i --save next-auth
Enter fullscreen mode Exit fullscreen mode

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:

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,
  },
});
Enter fullscreen mode Exit fullscreen mode

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

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'])),
    },
  };
}
Enter fullscreen mode Exit fullscreen mode

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:

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;
Enter fullscreen mode Exit fullscreen mode

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

Auth in Spanish

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

Top comments (2)

Collapse
 
javier_garzaburgos_3728f profile image
Javier Garza Burgos

Thank you Ángel. This is a great article. I was wondering if you are planning to write something similar for NextJs 13, using the experimental App folder.

Collapse
 
angelalexqc profile image
Ángel Quiroz

Yes, I have a app example, In this week I'm going to post it