De Pages a App

Esta guía te ayudará a:

Actualización

Versión de Node.js

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

Versión de Next.js

Para actualizar a Next.js versión 13, ejecuta el siguiente comando con tu gestor de paquetes preferido:

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

Versión de ESLint

Si estás usando ESLint, necesitas actualizar tu versión de ESLint:

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

Bueno saber: Puede que necesites reiniciar el servidor de ESLint en VS Code para que los cambios surtan efecto. Abre la Paleta de Comandos (cmd+shift+p en Mac; ctrl+shift+p en Windows) y busca ESLint: Restart ESLint Server.

Próximos pasos

Después de actualizar, consulta 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 nuevo App Router. Puedes seguir 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 ayudarte 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: Agrega 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 debes ejecutar next-image-to-legacy-image.

El componente <Link> ya no requiere agregar manualmente una etiqueta <a> como hijo. Este comportamiento se agregó como opción experimental en la versión 12.2 y ahora es el predeterminado. En Next.js 13, <Link> siempre renderiza <a> y te 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 tus enlaces a Next.js 13, puedes 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:

  • Mueve cualquier script beforeInteractive que hayas 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úrate de moverlos a un Componente del Cliente o eliminarlos por completo.

Optimización de fuentes

Anteriormente, Next.js te ayudaba a optimizar fuentes incrustando CSS de fuentes. La versión 13 introduce el nuevo módulo next/font que te permite personalizar tu experiencia de carga de fuentes mientras garantiza un gran rendimiento y privacidad. next/font es compatible tanto con los directorios pages como app.

Si bien incrustar CSS todavía funciona en pages, no funciona en app. Debes usar next/font en su lugar.

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

Migrando de pages a app

🎥 Mira: Aprende cómo adoptar incrementalmente el App Router → YouTube (16 minutos).

Mudarse al App Router puede ser la primera vez que uses 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 tu 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 página por página incremental.

  • El directorio app admite rutas anidadas y diseños. Aprende más.
  • Usa carpetas anidadas para definir rutas y un archivo especial page.js para hacer que un segmento de ruta sea accesible públicamente. Aprende más.
  • Se usan convenciones de archivos especiales para crear la interfaz de usuario para cada segmento de ruta. Los archivos especiales más comunes son page.js y layout.js.
    • Usa page.js para definir la interfaz de usuario única de una ruta.
    • Usa 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.
  • Puedes colocar otros archivos dentro del directorio app, como componentes, estilos, pruebas y más. Aprende más.
  • Las funciones de obtención de datos como getServerSideProps y getStaticProps han sido reemplazadas por una nueva API dentro de app. getStaticPaths ha sido reemplazado por generateStaticParams.
  • pages/_app.js y pages/_document.js han sido reemplazados por un único diseño raíz app/layout.js. Aprende más.
  • pages/_error.js ha sido reemplazado por archivos especiales error.js más granulares. Aprende más.
  • pages/404.js ha sido reemplazado por el archivo not-found.js.
  • Las Rutas API pages/api/* han sido reemplazadas por el archivo especial route.js (Manejador de Ruta).

Paso 1: Creando el directorio app

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

npm install next@latest

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

Paso 2: Creando un diseño raíz

Crea 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>
  )
}
export default function RootLayout({
  // Los diseños deben aceptar una prop children.
  // Esto se llenará con diseños anidados o páginas
  children,
}) {
  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 manejar elementos HTML <head>, puedes usar el soporte SEO incorporado:

import { Metadata } from 'next'

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

Migrando _document.js y _app.js

Si tienes un archivo _app o _document existente, puedes 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/*. Debes mantener _app/_document durante la migración para evitar que tus rutas pages/* se rompan. Una vez migrado completamente, puedes eliminarlos de forma segura.

Si estás usando proveedores de contexto de React, deberán moverse a un Componente del Cliente.

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

Next.js recomendaba agregar una propiedad a los componentes de página 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

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

    app/dashboard/page.js
    export default function Page() {
      return <p>My Page</p>
    }
  • Mueve el contenido de DashboardLayout a un nuevo Componente del Cliente para conservar 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>
      )
    }
  • Importa 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>
    }
  • Puedes mover gradualmente 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ías al cliente.

Paso 3: Migrando next/head

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

Antes:

import Head from 'next/head'

export default function Page() {
  return (
    <>
      <Head>
        <title>My page title</title>
      </Head>
    </>
  )
}
import Head from 'next/head'

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

Después:

import { Metadata } from 'next'

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

export default function Page() {
  return '...'
}
export const metadata = {
  title: 'My Page Title',
}

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

Consulta todas las opciones de metadata.

Paso 4: Migración de 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.

Nota importante: Este es el camino de migración más sencillo 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, añade 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 prerenderizar 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
// prerenderizado 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>
  )
}
'use client'

// Este es un Componente del Cliente. Recibe datos como props y
// tiene acceso a estado y efectos, igual que los componentes de Página
// en el directorio `pages`.
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 (data fetching APIs). 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} />
    }
    // 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: Migración de Hooks de Enrutamiento

Se ha añadido 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. Usa el hook separado useSearchParams 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 con 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()

  // ...
}
'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 características 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 prerenderizado y en su lugar se renderizará en el cliente en tiempo de ejecución.

Consulta la referencia de la API de useRouter().

Paso 6: Migración de 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 petición.
  // 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>
}
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 petición.
  // 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 prerenderiza 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 directorio app, 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>
  )
}
// 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>
  )
}

Acceso 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 = 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 = cookies().get('theme')
  const data = await getData()
  return '...'
}
// Directorio `app`
import { cookies, headers } from 'next/headers'

async function getData() {
  const authHeader = 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 = cookies().get('theme')
  const data = await getData()
  return '...'
}

Generación de Sitio Estático (getStaticProps)

En el directorio pages, la función getStaticProps se usa para prerrenderizar 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 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 tiempo de compilació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/${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.


Reemplazo de 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 tiempo de compilación. Esta propiedad puede establecerse en 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 establecido en 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 determinada usando las APIs Web Request y Response.

export async function GET(request: Request) {}
export async function GET(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.

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á utilizando Tailwind CSS, deberá 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

Codemods

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