Cómo migrar de Pages al App Router

Esta guía le ayudará a:

Actualización

Versión de Node.js

La versión mínima de Node.js es ahora v18.17. Consulte la documentación de Node.js para más información.

Versión de Next.js

Para actualizar a Next.js versión 13, ejecute el siguiente comando usando su gestor de paquetes preferido:

Terminal
npm install next@latest react@latest react-dom@latest

Versión de ESLint

Si está usando ESLint, necesita actualizar su versión de ESLint:

Terminal
npm install -D eslint-config-next@latest

Nota importante: Puede que necesite reiniciar el servidor de ESLint en VS Code para que los cambios surtan efecto. Abra la Paleta de Comandos (cmd+shift+p en Mac; ctrl+shift+p en Windows) y busque ESLint: Restart ESLint Server.

Próximos pasos

Después de actualizar, consulte las siguientes secciones para los próximos pasos:

Mejorando nuevas características

Next.js 13 introdujo el nuevo App Router con nuevas características y convenciones. El nuevo Router está disponible en el directorio app y coexiste con el directorio pages.

Actualizar a Next.js 13 no requiere usar el App Router. Puede continuar usando pages con nuevas características que funcionan en ambos directorios, como el componente Image actualizado, el componente Link, el componente Script y la optimización de fuentes.

Componente <Image/>

Next.js 12 introdujo mejoras al Componente Image con una importación temporal: next/future/image. Estas mejoras incluían menos JavaScript del lado del cliente, formas más fáciles de extender y estilizar imágenes, mejor accesibilidad y carga diferida nativa del navegador.

En la versión 13, este nuevo comportamiento es ahora el predeterminado para next/image.

Hay dos codemods para ayudarle a migrar al nuevo Componente Image:

  • Codemod next-image-to-legacy-image: Renombra de forma segura y automática las importaciones de next/image a next/legacy/image. Los componentes existentes mantendrán el mismo comportamiento.
  • Codemod next-image-experimental: Añade peligrosamente estilos en línea y elimina props no utilizados. Esto cambiará el comportamiento de los componentes existentes para que coincidan con los nuevos valores predeterminados. Para usar este codemod, primero debe ejecutar el codemod next-image-to-legacy-image.

El Componente <Link> ya no requiere añadir manualmente una etiqueta <a> como hijo. Este comportamiento se añadió como una opción experimental en la versión 12.2 y ahora es el predeterminado. En Next.js 13, <Link> siempre renderiza <a> y permite pasar props a la etiqueta subyacente.

Por ejemplo:

import Link from 'next/link'

// Next.js 12: `<a>` debe estar anidada, de lo contrario se excluye
<Link href="/about">
  <a>About</a>
</Link>

// Next.js 13: `<Link>` siempre renderiza `<a>` internamente
<Link href="/about">
  About
</Link>

Para actualizar sus enlaces a Next.js 13, puede usar el codemod new-link.

Componente <Script>

El comportamiento de next/script se ha actualizado para soportar tanto pages como app, pero se necesitan algunos cambios para garantizar una migración fluida:

  • Mueva cualquier script beforeInteractive que haya incluido previamente en _document.js al archivo de diseño raíz (app/layout.tsx).
  • La estrategia experimental worker aún no funciona en app y los scripts con esta estrategia deberán eliminarse o modificarse para usar una estrategia diferente (por ejemplo, lazyOnload).
  • Los manejadores onLoad, onReady y onError no funcionarán en Componentes del Servidor, así que asegúrese de moverlos a un Componente del Cliente o eliminarlos por completo.

Optimización de fuentes

Anteriormente, Next.js le ayudaba a optimizar fuentes mediante CSS en línea. La versión 13 introduce el nuevo módulo next/font que le permite personalizar su experiencia de carga de fuentes manteniendo un gran rendimiento y privacidad. next/font es compatible tanto con los directorios pages como app.

Aunque CSS en línea sigue funcionando en pages, no funciona en app. Debe usar next/font en su lugar.

Consulte la página Optimización de fuentes para aprender a usar next/font.

Migrando de pages a app

🎥 Ver: Aprenda a adoptar incrementalmente el App Router → YouTube (16 minutos).

Mudarse al App Router puede ser la primera vez que use características de React en las que Next.js se basa, como Componentes del Servidor, Suspense y más. Cuando se combinan con nuevas características de Next.js como archivos especiales y diseños, la migración implica nuevos conceptos, modelos mentales y cambios de comportamiento que aprender.

Recomendamos reducir la complejidad combinada de estas actualizaciones dividiendo su migración en pasos más pequeños. El directorio app está diseñado intencionalmente para funcionar simultáneamente con el directorio pages para permitir una migración incremental página por página.

  • El directorio app soporta rutas anidadas y diseños. Aprenda más.
  • Use carpetas anidadas para definir rutas y un archivo especial page.js para hacer un segmento de ruta accesible públicamente. Aprenda más.
  • Se usan convenciones de archivos especiales para crear la interfaz de usuario de cada segmento de ruta. Los archivos especiales más comunes son page.js y layout.js.
    • Use page.js para definir la interfaz de usuario única de una ruta.
    • Use layout.js para definir la interfaz de usuario compartida entre múltiples rutas.
    • Se pueden usar extensiones de archivo .js, .jsx o .tsx para archivos especiales.
  • Puede colocar otros archivos dentro del directorio app como componentes, estilos, pruebas y más. Aprenda más.
  • Las funciones de obtención de datos como getServerSideProps y getStaticProps han sido reemplazadas con una nueva API dentro de app. getStaticPaths ha sido reemplazado con generateStaticParams.
  • pages/_app.js y pages/_document.js han sido reemplazados con un único diseño raíz app/layout.js. Aprenda más.
  • pages/_error.js ha sido reemplazado con archivos especiales error.js más granulares. Aprenda más.
  • pages/404.js ha sido reemplazado con el archivo not-found.js.
  • Las Rutas API pages/api/* han sido reemplazadas con el archivo especial route.js (Manejador de Ruta).

Paso 1: Creando el directorio app

Actualice a la última versión de Next.js (requiere 13.4 o superior):

npm install next@latest

Luego, cree un nuevo directorio app en la raíz de su proyecto (o directorio src/).

Paso 2: Creando un Diseño Raíz

Cree un nuevo archivo app/layout.tsx dentro del directorio app. Este es un diseño raíz que se aplicará a todas las rutas dentro de app.

export default function RootLayout({
  // Los diseños deben aceptar una prop children.
  // Esto se llenará con diseños anidados o páginas
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}
  • El directorio app debe incluir un diseño raíz.
  • El diseño raíz debe definir las etiquetas <html> y <body> ya que Next.js no las crea automáticamente.
  • El diseño raíz reemplaza los archivos pages/_app.tsx y pages/_document.tsx.
  • Se pueden usar extensiones .js, .jsx o .tsx para archivos de diseño.

Para gestionar elementos HTML <head>, puede usar el soporte SEO integrado:

import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: 'Home',
  description: 'Welcome to Next.js',
}

Migrando _document.js y _app.js

Si tiene un archivo _app o _document existente, puede copiar el contenido (por ejemplo, estilos globales) al diseño raíz (app/layout.tsx). Los estilos en app/layout.tsx no se aplicarán a pages/*. Debe mantener _app/_document durante la migración para evitar que sus rutas pages/* se rompan. Una vez migrado completamente, puede eliminarlos de forma segura.

Si está usando cualquier proveedor de contexto de React, deberá moverlos a un Componente del Cliente.

Migrando el patrón getLayout() a Diseños (Opcional)

Next.js recomendaba añadir una propiedad a los componentes Page para lograr diseños por página en el directorio pages. Este patrón puede reemplazarse con el soporte nativo para diseños anidados en el directorio app.

Ver ejemplo antes y después

Antes

components/DashboardLayout.js
export default function DashboardLayout({ children }) {
  return (
    <div>
      <h2>My Dashboard</h2>
      {children}
    </div>
  )
}
pages/dashboard/index.js
import DashboardLayout from '../components/DashboardLayout'

export default function Page() {
  return <p>My Page</p>
}

Page.getLayout = function getLayout(page) {
  return <DashboardLayout>{page}</DashboardLayout>
}

Después

  • Elimine la propiedad Page.getLayout de pages/dashboard/index.js y siga los pasos para migrar páginas al directorio app.

    app/dashboard/page.js
    export default function Page() {
      return <p>My Page</p>
    }
  • Mueva el contenido de DashboardLayout a un nuevo Componente del Cliente para mantener el comportamiento del directorio pages.

    app/dashboard/DashboardLayout.js
    'use client' // esta directiva debe estar al inicio del archivo, antes de cualquier importación.
    
    // Este es un Componente del Cliente
    export default function DashboardLayout({ children }) {
      return (
        <div>
          <h2>My Dashboard</h2>
          {children}
        </div>
      )
    }
  • Importe DashboardLayout en un nuevo archivo layout.js dentro del directorio app.

    app/dashboard/layout.js
    import DashboardLayout from './DashboardLayout'
    
    // Este es un Componente del Servidor
    export default function Layout({ children }) {
      return <DashboardLayout>{children}</DashboardLayout>
    }
  • Puede mover incrementalmente partes no interactivas de DashboardLayout.js (Componente del Cliente) a layout.js (Componente del Servidor) para reducir la cantidad de JavaScript del componente que envía al cliente.

Paso 3: Migrando next/head

En el directorio pages, el componente React next/head se usa para gestionar elementos HTML <head> como title y meta. En el directorio app, next/head se reemplaza con el nuevo soporte SEO integrado.

Antes:

import Head from 'next/head'

export default function Page() {
  return (
    <>
      <Head>
        <title>My page title</title>
      </Head>
    </>
  )
}

Después:

import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: 'My Page Title',
}

export default function Page() {
  return '...'
}

Consulte todas las opciones de metadata.

Paso 4: Migrar páginas

Recomendamos dividir la migración de una página en dos pasos principales:

  • Paso 1: Mover el Componente de Página exportado por defecto a un nuevo Componente del Cliente.
  • Paso 2: Importar el nuevo Componente del Cliente en un archivo page.js dentro del directorio app.

Bueno saber: Este es el camino de migración más fácil porque tiene el comportamiento más comparable al directorio pages.

Paso 1: Crear un nuevo Componente del Cliente

  • Crea un nuevo archivo separado dentro del directorio app (ej. app/home-page.tsx o similar) que exporte un Componente del Cliente. Para definir Componentes del Cliente, agrega la directiva 'use client' al inicio del archivo (antes de cualquier importación).
    • Similar al Enrutador de Páginas (Pages Router), hay un paso de optimización para pre-renderizar Componentes del Cliente a HTML estático en la carga inicial de la página.
  • Mueve el componente de página exportado por defecto desde pages/index.js a app/home-page.tsx.
'use client'

// Este es un Componente del Cliente (igual que los componentes en el directorio `pages`)
// Recibe datos como props, tiene acceso a estado y efectos, y es
// pre-renderizado en el servidor durante la carga inicial de la página.
export default function HomePage({ recentPosts }) {
  return (
    <div>
      {recentPosts.map((post) => (
        <div key={post.id}>{post.title}</div>
      ))}
    </div>
  )
}

Paso 2: Crear una nueva página

  • Crea un nuevo archivo app/page.tsx dentro del directorio app. Este es un Componente del Servidor por defecto.

  • Importa el Componente del Cliente home-page.tsx en la página.

  • Si estabas obteniendo datos en pages/index.js, mueve la lógica de obtención de datos directamente al Componente del Servidor usando las nuevas APIs de obtención de datos. Consulta la guía de actualización de obtención de datos para más detalles.

    // Importa tu Componente del Cliente
    import HomePage from './home-page'
    
    async function getPosts() {
      const res = await fetch('https://...')
      const posts = await res.json()
      return posts
    }
    
    export default async function Page() {
      // Obtén datos directamente en un Componente del Servidor
      const recentPosts = await getPosts()
      // Pasa los datos obtenidos a tu Componente del Cliente
      return <HomePage recentPosts={recentPosts} />
    }
  • Si tu página anterior usaba useRouter, necesitarás actualizar a los nuevos hooks de enrutamiento. Aprende más.

  • Inicia tu servidor de desarrollo y visita http://localhost:3000. Deberías ver tu ruta de índice existente, ahora servida a través del directorio app.

Paso 5: Migrar Hooks de Enrutamiento

Se ha agregado un nuevo enrutador para soportar el nuevo comportamiento en el directorio app.

En app, deberías usar los tres nuevos hooks importados desde next/navigation: useRouter(), usePathname(), y useSearchParams().

  • El nuevo hook useRouter se importa desde next/navigation y tiene un comportamiento diferente al hook useRouter en pages que se importa desde next/router.
  • El nuevo useRouter no devuelve el string pathname. Usa el hook separado usePathname en su lugar.
  • El nuevo useRouter no devuelve el objeto query. Los parámetros de búsqueda (search params) y los parámetros de ruta dinámica ahora están separados. Usa los hooks useSearchParams y useParams en su lugar.
  • Puedes usar useSearchParams y usePathname juntos para escuchar cambios de página. Consulta la sección Eventos del Enrutador (Router Events) para más detalles.
  • Estos nuevos hooks solo son compatibles en Componentes del Cliente. No pueden usarse en Componentes del Servidor.
'use client'

import { useRouter, usePathname, useSearchParams } from 'next/navigation'

export default function ExampleClientComponent() {
  const router = useRouter()
  const pathname = usePathname()
  const searchParams = useSearchParams()

  // ...
}

Además, el nuevo hook useRouter tiene los siguientes cambios:

  • isFallback ha sido eliminado porque fallback ha sido reemplazado.
  • Los valores locale, locales, defaultLocales, domainLocales han sido eliminados porque las funciones integradas de i18n de Next.js ya no son necesarias en el directorio app. Aprende más sobre i18n.
  • basePath ha sido eliminado. La alternativa no será parte de useRouter. Aún no se ha implementado.
  • asPath ha sido eliminado porque el concepto de as ha sido eliminado del nuevo enrutador.
  • isReady ha sido eliminado porque ya no es necesario. Durante el renderizado estático (static rendering), cualquier componente que use el hook useSearchParams() omitirá el paso de pre-renderizado y en su lugar se renderizará en el cliente en tiempo de ejecución.
  • route ha sido eliminado. usePathname o useSelectedLayoutSegments() proporcionan una alternativa.

Consulta la referencia de la API de useRouter().

Compartir componentes entre pages y app

Para mantener componentes compatibles entre los enrutadores pages y app, consulta el hook useRouter desde next/compat/router. Este es el hook useRouter del directorio pages, pero destinado a usarse al compartir componentes entre enrutadores. Cuando estés listo para usarlo solo en el enrutador app, actualiza al nuevo useRouter desde next/navigation.

Paso 6: Migrar Métodos de Obtención de Datos

El directorio pages usa getServerSideProps y getStaticProps para obtener datos para las páginas. Dentro del directorio app, estas funciones anteriores de obtención de datos son reemplazadas por una API más simple construida sobre fetch() y Componentes del Servidor React asíncronos.

export default async function Page() {
  // Esta solicitud debe almacenarse en caché hasta que se invalide manualmente.
  // Similar a `getStaticProps`.
  // `force-cache` es el valor por defecto y puede omitirse.
  const staticData = await fetch(`https://...`, { cache: 'force-cache' })

  // Esta solicitud debe volver a obtenerse en cada solicitud.
  // Similar a `getServerSideProps`.
  const dynamicData = await fetch(`https://...`, { cache: 'no-store' })

  // Esta solicitud debe almacenarse en caché con un tiempo de vida de 10 segundos.
  // Similar a `getStaticProps` con la opción `revalidate`.
  const revalidatedData = await fetch(`https://...`, {
    next: { revalidate: 10 },
  })

  return <div>...</div>
}

Renderizado del Lado del Servidor (getServerSideProps)

En el directorio pages, getServerSideProps se usa para obtener datos en el servidor y pasar props al componente React exportado por defecto en el archivo. El HTML inicial para la página se pre-renderiza desde el servidor, seguido de "hidratar" la página en el navegador (haciéndola interactiva).

pages/dashboard.js
// Directorio `pages`

export async function getServerSideProps() {
  const res = await fetch(`https://...`)
  const projects = await res.json()

  return { props: { projects } }
}

export default function Dashboard({ projects }) {
  return (
    <ul>
      {projects.map((project) => (
        <li key={project.id}>{project.name}</li>
      ))}
    </ul>
  )
}

En el Enrutador de la Aplicación (App Router), podemos colocar nuestra obtención de datos dentro de nuestros componentes React usando Componentes del Servidor (Server Components). Esto nos permite enviar menos JavaScript al cliente, manteniendo el HTML renderizado desde el servidor.

Al establecer la opción cache en no-store, podemos indicar que los datos obtenidos nunca deben almacenarse en caché. Esto es similar a getServerSideProps en el directorio pages.

// Directorio `app`

// Esta función puede llamarse de cualquier manera
async function getProjects() {
  const res = await fetch(`https://...`, { cache: 'no-store' })
  const projects = await res.json()

  return projects
}

export default async function Dashboard() {
  const projects = await getProjects()

  return (
    <ul>
      {projects.map((project) => (
        <li key={project.id}>{project.name}</li>
      ))}
    </ul>
  )
}

Acceder al Objeto de Solicitud

En el directorio pages, puedes recuperar datos basados en solicitudes según la API HTTP de Node.js.

Por ejemplo, puedes recuperar el objeto req desde getServerSideProps y usarlo para obtener las cookies y cabeceras de la solicitud.

pages/index.js
// Directorio `pages`

export async function getServerSideProps({ req, query }) {
  const authHeader = req.getHeaders()['authorization'];
  const theme = req.cookies['theme'];

  return { props: { ... }}
}

export default function Page(props) {
  return ...
}

El directorio app expone nuevas funciones de solo lectura para recuperar datos de solicitud:

// Directorio `app`
import { cookies, headers } from 'next/headers'

async function getData() {
  const authHeader = (await headers()).get('authorization')

  return '...'
}

export default async function Page() {
  // Puedes usar `cookies` o `headers` dentro de Componentes del Servidor
  // directamente o en tu función de obtención de datos
  const theme = (await cookies()).get('theme')
  const data = await getData()
  return '...'
}

Generación de Sitios Estáticos (getStaticProps)

En el directorio pages, la función getStaticProps se usa para pre-renderizar una página en tiempo de compilación. Esta función puede usarse para obtener datos desde una API externa o directamente desde una base de datos, y pasar estos datos a toda la página mientras se genera durante la compilación.

pages/index.js
// Directorio `pages`

export async function getStaticProps() {
  const res = await fetch(`https://...`)
  const projects = await res.json()

  return { props: { projects } }
}

export default function Index({ projects }) {
  return projects.map((project) => <div>{project.name}</div>)
}

En el directorio app, la obtención de datos con fetch() tendrá por defecto cache: 'force-cache', lo que almacenará en caché los datos de la solicitud hasta que se invaliden manualmente. Esto es similar a getStaticProps en el directorio pages.

app/page.js
// Directorio `app`

// Esta función puede llamarse de cualquier manera
async function getProjects() {
  const res = await fetch(`https://...`)
  const projects = await res.json()

  return projects
}

export default async function Index() {
  const projects = await getProjects()

  return projects.map((project) => <div>{project.name}</div>)
}

Rutas dinámicas (getStaticPaths)

En el directorio pages, la función getStaticPaths se utiliza para definir las rutas dinámicas que deben ser pre-renderizadas en el momento de construcción.

pages/posts/[id].js
// Directorio `pages`
import PostLayout from '@/components/post-layout'

export async function getStaticPaths() {
  return {
    paths: [{ params: { id: '1' } }, { params: { id: '2' } }],
  }
}

export async function getStaticProps({ params }) {
  const res = await fetch(`https://.../posts/${params.id}`)
  const post = await res.json()

  return { props: { post } }
}

export default function Post({ post }) {
  return <PostLayout post={post} />
}

En el directorio app, getStaticPaths se reemplaza con generateStaticParams.

generateStaticParams se comporta de manera similar a getStaticPaths, pero tiene una API simplificada para devolver parámetros de ruta y puede usarse dentro de layouts. La forma de retorno de generateStaticParams es un array de segmentos en lugar de un array de objetos param anidados o una cadena de rutas resueltas.

app/posts/[id]/page.js
// Directorio `app`
import PostLayout from '@/components/post-layout'

export async function generateStaticParams() {
  return [{ id: '1' }, { id: '2' }]
}

async function getPost(params) {
  const res = await fetch(`https://.../posts/${(await params).id}`)
  const post = await res.json()

  return post
}

export default async function Post({ params }) {
  const post = await getPost(params)

  return <PostLayout post={post} />
}

Usar el nombre generateStaticParams es más apropiado que getStaticPaths para el nuevo modelo en el directorio app. El prefijo get se reemplaza con un generate más descriptivo, que encaja mejor ahora que getStaticProps y getServerSideProps ya no son necesarios. El sufijo Paths se reemplaza por Params, que es más apropiado para el enrutamiento anidado con múltiples segmentos dinámicos.


Reemplazando fallback

En el directorio pages, la propiedad fallback devuelta por getStaticPaths se utiliza para definir el comportamiento de una página que no se ha pre-renderizado en el momento de construcción. Esta propiedad puede configurarse como true para mostrar una página de respaldo mientras se genera la página, false para mostrar una página 404, o blocking para generar la página en el momento de la solicitud.

pages/posts/[id].js
// Directorio `pages`

export async function getStaticPaths() {
  return {
    paths: [],
    fallback: 'blocking'
  };
}

export async function getStaticProps({ params }) {
  ...
}

export default function Post({ post }) {
  return ...
}

En el directorio app, la propiedad config.dynamicParams controla cómo se manejan los parámetros fuera de generateStaticParams:

  • true: (predeterminado) Los segmentos dinámicos no incluidos en generateStaticParams se generan bajo demanda.
  • false: Los segmentos dinámicos no incluidos en generateStaticParams devolverán un 404.

Esto reemplaza la opción fallback: true | false | 'blocking' de getStaticPaths en el directorio pages. La opción fallback: 'blocking' no está incluida en dynamicParams porque la diferencia entre 'blocking' y true es insignificante con streaming.

app/posts/[id]/page.js
// Directorio `app`

export const dynamicParams = true;

export async function generateStaticParams() {
  return [...]
}

async function getPost(params) {
  ...
}

export default async function Post({ params }) {
  const post = await getPost(params);

  return ...
}

Con dynamicParams configurado como true (el valor predeterminado), cuando se solicita un segmento de ruta que no ha sido generado, se renderizará en el servidor y se almacenará en caché.

Regeneración estática incremental (getStaticProps con revalidate)

En el directorio pages, la función getStaticProps permite agregar un campo revalidate para regenerar automáticamente una página después de un cierto tiempo.

pages/index.js
// Directorio `pages`

export async function getStaticProps() {
  const res = await fetch(`https://.../posts`)
  const posts = await res.json()

  return {
    props: { posts },
    revalidate: 60,
  }
}

export default function Index({ posts }) {
  return (
    <Layout>
      <PostList posts={posts} />
    </Layout>
  )
}

En el directorio app, la obtención de datos con fetch() puede usar revalidate, que almacenará en caché la solicitud durante la cantidad de segundos especificada.

app/page.js
// Directorio `app`

async function getPosts() {
  const res = await fetch(`https://.../posts`, { next: { revalidate: 60 } })
  const data = await res.json()

  return data.posts
}

export default async function PostList() {
  const posts = await getPosts()

  return posts.map((post) => <div>{post.name}</div>)
}

Rutas API

Las Rutas API continúan funcionando en el directorio pages/api sin cambios. Sin embargo, han sido reemplazadas por Route Handlers en el directorio app.

Los Route Handlers permiten crear manejadores de solicitudes personalizados para una ruta dada usando las APIs Web Request y Response.

export async function GET(request: Request) {}

Nota importante: Si anteriormente usaba rutas API para llamar a una API externa desde el cliente, ahora puede usar Server Components en su lugar para obtener datos de forma segura. Más información sobre obtención de datos.

Aplicaciones de una sola página (SPA)

Si también está migrando a Next.js desde una Aplicación de una sola página (SPA) al mismo tiempo, consulte nuestra documentación para obtener más información.

Paso 7: Estilos

En el directorio pages, las hojas de estilo globales están restringidas solo a pages/_app.js. Con el directorio app, esta restricción se ha eliminado. Los estilos globales se pueden agregar a cualquier layout, página o componente.

Tailwind CSS

Si está usando Tailwind CSS, necesitará agregar el directorio app a su archivo tailwind.config.js:

tailwind.config.js
module.exports = {
  content: [
    './app/**/*.{js,ts,jsx,tsx,mdx}', // <-- Agregar esta línea
    './pages/**/*.{js,ts,jsx,tsx,mdx}',
    './components/**/*.{js,ts,jsx,tsx,mdx}',
  ],
}

También necesitará importar sus estilos globales en su archivo app/layout.js:

app/layout.js
import '../styles/globals.css'

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}

Más información sobre estilos con Tailwind CSS

Usando App Router junto con Pages Router

Al navegar entre rutas servidas por los diferentes routers de Next.js, habrá una navegación dura. El prefetching automático de enlaces con next/link no hará prefetch entre routers.

En su lugar, puede optimizar navegaciones entre App Router y Pages Router para conservar las transiciones de página prefetch y rápidas. Más información.

Codemods

Next.js proporciona transformaciones Codemod para ayudar a actualizar su base de código cuando una función está obsoleta. Consulte Codemods para más información.