Usar next-i18next & NextAuth.js en la misma aplicación de NextJS
Tags: nextjs, typescript, tutorial, programming
Published: 2022-04-17 15:57:55

Este es mi primer tutorial, la intención de este es facilitar la vida y ahorrar el tiempo de quien necesite implementar estas tecnologías en un mismo proyecto, a mí me costó un poco implementarlo y quiero compartirlo con quien lo pueda necesitar.
Espero les sirva...

## 1. Creamos un nuevo proyecto como indica [la documentación](https://nextjs.org/docs/getting-started).
Lo voy a hacer con Typescript pero con Javascript los pasos son los mismos.
```bash
npx create-next-app@latest --typescript
```
Escribes el nombre de tu proyecto y esperas a que se instalen las dependencias, luego ejecutas VSCode o tu editor de código en la carpeta del proyecto.
Ahora en la terminal escribes 
```bash
npm run dev
```
Esto iniciará el servidor de desarrollo en el puerto 3000.

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

## 2. Instalamos y configuramos [next-i18next](https://github.com/isaachinman/next-i18next)
Como indica el repositorio del proyecto ejecutamos
```bash
yarn add next-i18next 
# Si usas NPM ejecuta:
npm i --save next-i18next
```

Ahora creamos dos carpetas con archivos json para probar las traducciones:
```
└─ public
   └ locales
     ├─en
     | └─ common.json
     └─es
       └─ common.json
```
Después creamos un archivo `next-i18next.config.js` en la raíz del proyecto con el siguiente contenido:
```js
module.exports = {
  i18n: {
    defaultLocale: 'en',
    locales: ['en', 'es'],
  },
};
```
Luego en el archivo `next.config.js` importamos la configuración:
```js
/** @type {import('next').NextConfig} */
const { i18n } = require("./next-i18next.config");
const nextConfig = {
  reactStrictMode: true,
  i18n,
};

module.exports = nextConfig;
```
Finalmente editamos el archivo `pages/_app.tsx` para ejecutar la aplicación con traducciones, el cambio se hace en la última línea.

```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. Agregamos y probamos las traducciones.
En los archivos `public/locales/en/common.json` y `public/locales/es/common.json` agregamos las traducciones de esta manera:
`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"
}
```

Ahora, este paso es importante, para que las traducciones funcionen correctamente, deben ser llamadas desde el servidor en todos los componentes a nivel de página para que este realice la "hidratación" de manera correcta, en este caso editamos nuestro archivo `pages/index.tsx` para pasar las traducciones, para poder llamar cada traducción de manera individual usamos

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

El archivo en este caso queda así:

```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'])),
    },
  };
}
```

El resultado es la página traducida con el idioma detectado en el sistema.
![Translated Page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/foq27bs3fp2njh7i0i2a.png)

Si queremos probar manualmente el cambio de idioma hay que cambiar la configuración, además agregaré otra línea que servirá para subir nuestro proyecto a [Vercel](https://vercel.com/), el archivo queda así:

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

module.exports = {
  i18n: {
    defaultLocale: 'en',
    locales: ['en', 'es'],
    localeDetection: false,
    localePath: path.resolve('./public/locales'), // for deployment on Vercel
  },
};
```
También crearemos un boton para hacer el cambio de idioma de manera manual, el archivo `pages/index.tsx` quedaría así:

```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'])),
    },
  };
}
```

De esta forma ya tenemos nuestra aplicación trabajando perfectamente con dos idiomas basados en la ruta


## 4. Configuramos la autenticación con [next-auth](https://next-auth.js.org)

Primero instalamos el paquete:
```bash
npm i --save next-auth
```

Para propósitos de prueba usaremos credenciales (email y contraseña), además haremos una validación sencilla, el hacerla de manera correcta depende de lo que quieras en tu proyecto, si lo comentas puedo hacer un tutorial explicando cómo hacer la autenticación con diferentes proveedores y usando una página de inicio customizable. Dicho eso seguimos...

Creamos un archivo `pages/api/auth/[...nextauth].ts`
Con el siguiente contenido:

```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,
  },
});
```

Luego aditamos el archivo `pages/index.tsx` para tener un botón de autenticación o de logout además del cambio de idioma.

```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'])),
    },
  };
}
```

Ahora lo más importante, seguramente la razón por la que vienes a este post, y personalmente lo que más me costó solucionar, solo hay que hacer un pequeño cambio en el archivo `pages/_app.tsx` para que el SessionProvider no choque con el Provider de Traducción:

```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;
```

¡Y listo!, ahora podemos cambiar el idioma de nuestra página y tener la autenticación en la misma aplicación de NextJS.

![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)


Espero haberte ayudado, si tienes alguna observación o duda déjala en los comentarios para ayudar a más desarrolladores.