Cómo migrar de Create React App a Next.js

Esta guía te ayudará a migrar un sitio existente de Create React App (CRA) a Next.js.

¿Por qué cambiar?

Existen varias razones por las que podrías querer cambiar de Create React App a Next.js:

Tiempo de carga inicial lento

Create React App utiliza React exclusivamente del lado del cliente. Las aplicaciones solo del lado del cliente, también conocidas como aplicaciones de una sola página (SPAs), suelen experimentar tiempos de carga inicial lentos. Esto ocurre por un par de razones:

  1. El navegador necesita esperar a que el código de React y todo el paquete de tu aplicación se descarguen y ejecuten antes de que tu código pueda enviar solicitudes para cargar datos.
  2. El código de tu aplicación crece con cada nueva función y dependencia que agregas.

No hay división de código automática

El problema anterior de tiempos de carga lentos puede mitigarse parcialmente con la división de código. Sin embargo, si intentas hacer la división de código manualmente, puedes introducir inadvertidamente cascadas de red. Next.js proporciona división de código automática y eliminación de código muerto integrada en su enrutador y pipeline de construcción.

Cascadas de red

Una causa común de bajo rendimiento ocurre cuando las aplicaciones realizan solicitudes cliente-servidor secuenciales para obtener datos. Un patrón común para la obtención de datos en una SPA es renderizar un marcador de posición y luego obtener los datos después de que el componente se haya montado. Desafortunadamente, un componente hijo solo puede comenzar a obtener datos después de que su padre haya terminado de cargar sus propios datos, lo que resulta en una "cascada" de solicitudes.

Si bien la obtención de datos del lado del cliente es compatible con Next.js, Next.js también te permite mover la obtención de datos al servidor. Esto a menudo elimina por completo las cascadas cliente-servidor.

Estados de carga rápidos e intencionales

Con soporte integrado para transmisión mediante React Suspense, puedes definir qué partes de tu interfaz de usuario se cargan primero y en qué orden, sin crear cascadas de red.

Esto te permite construir páginas que se cargan más rápido y eliminar cambios de diseño.

Elige la estrategia de obtención de datos

Dependiendo de tus necesidades, Next.js te permite elegir tu estrategia de obtención de datos a nivel de página o componente. Por ejemplo, podrías obtener datos de tu CMS y renderizar publicaciones de blog en tiempo de construcción (SSG) para velocidades de carga rápidas, u obtener datos en tiempo de solicitud (SSR) cuando sea necesario.

Middleware

Next.js Middleware te permite ejecutar código en el servidor antes de que se complete una solicitud. Por ejemplo, puedes evitar un destello de contenido no autenticado redirigiendo a un usuario a una página de inicio de sesión en el middleware para páginas solo autenticadas. También puedes usarlo para funciones como pruebas A/B, experimentación y internacionalización.

Optimizaciones integradas

Imágenes, fuentes y scripts de terceros suelen tener un gran impacto en el rendimiento de una aplicación. Next.js incluye componentes y APIs especializados que los optimizan automáticamente por ti.

Pasos de migración

Nuestro objetivo es obtener una aplicación Next.js funcional lo más rápido posible para que luego puedas adoptar las funciones de Next.js incrementalmente. Para empezar, trataremos tu aplicación como una aplicación puramente del lado del cliente (SPA) sin reemplazar inmediatamente tu enrutador existente. Esto reduce la complejidad y los conflictos de fusión.

Nota: Si estás usando configuraciones avanzadas de CRA como un campo homepage personalizado en tu package.json, un service worker personalizado o ajustes específicos de Babel/webpack, consulta la sección Consideraciones adicionales al final de esta guía para obtener consejos sobre cómo replicar o adaptar estas funciones en Next.js.

Paso 1: Instalar la dependencia de Next.js

Instala Next.js en tu proyecto existente:

Terminal
npm install next@latest

Paso 2: Crear el archivo de configuración de Next.js

Crea un next.config.ts en la raíz de tu proyecto (mismo nivel que tu package.json). Este archivo contiene tus opciones de configuración de Next.js.

next.config.ts
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  output: 'export', // Genera una aplicación de una sola página (SPA)
  distDir: 'build', // Cambia el directorio de salida de construcción a `build`
}

export default nextConfig

Nota: Usar output: 'export' significa que estás haciendo una exportación estática. No tendrás acceso a funciones del lado del servidor como SSR o APIs. Puedes eliminar esta línea para aprovechar las funciones del servidor de Next.js.

Paso 3: Crear el diseño raíz

Una aplicación de App Router de Next.js debe incluir un archivo de diseño raíz, que es un componente de servidor React que envolverá todas tus páginas.

El equivalente más cercano al archivo de diseño raíz en una aplicación CRA es public/index.html, que incluye tus etiquetas <html>, <head> y <body>.

  1. Crea un nuevo directorio app dentro de tu carpeta src (o en la raíz de tu proyecto si prefieres app en la raíz).
  2. Dentro del directorio app, crea un archivo layout.tsx (o layout.js):
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return '...'
}
export default function RootLayout({ children }) {
  return '...'
}

Ahora copia el contenido de tu antiguo index.html en este componente <RootLayout>. Reemplaza body div#root (y body noscript) con <div id="root">{children}</div>.

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <head>
        <meta charSet="UTF-8" />
        <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <title>React App</title>
        <meta name="description" content="Web site created..." />
      </head>
      <body>
        <div id="root">{children}</div>
      </body>
    </html>
  )
}
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <title>React App</title>
        <meta name="description" content="Web site created..." />
      </head>
      <body>
        <div id="root">{children}</div>
      </body>
    </html>
  )
}

Bueno saber: Next.js ignora public/manifest.json de CRA, iconografía adicional y configuración de pruebas por defecto. Si necesitas esto, Next.js tiene soporte con su API de Metadata y configuración de Testing.

Paso 4: Metadatos

Next.js incluye automáticamente las etiquetas <meta charset="UTF-8" /> y <meta name="viewport" content="width=device-width, initial-scale=1" />, por lo que puedes eliminarlas de <head>:

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <head>
        <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
        <title>React App</title>
        <meta name="description" content="Web site created..." />
      </head>
      <body>
        <div id="root">{children}</div>
      </body>
    </html>
  )
}
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <head>
        <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
        <title>React App</title>
        <meta name="description" content="Web site created..." />
      </head>
      <body>
        <div id="root">{children}</div>
      </body>
    </html>
  )
}

Cualquier archivo de metadatos como favicon.ico, icon.png, robots.txt se agrega automáticamente a la etiqueta <head> de la aplicación siempre que los coloques en el nivel superior del directorio app. Después de mover todos los archivos compatibles al directorio app, puedes eliminar sus etiquetas <link> con seguridad:

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <head>
        <title>React App</title>
        <meta name="description" content="Web site created..." />
      </head>
      <body>
        <div id="root">{children}</div>
      </body>
    </html>
  )
}
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <head>
        <title>React App</title>
        <meta name="description" content="Web site created..." />
      </head>
      <body>
        <div id="root">{children}</div>
      </body>
    </html>
  )
}

Finalmente, Next.js puede gestionar tus últimas etiquetas <head> con la API de Metadata. Mueve tu información final de metadatos a un objeto metadata exportado:

import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: 'React App',
  description: 'Web site created with Next.js.',
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>
        <div id="root">{children}</div>
      </body>
    </html>
  )
}
export const metadata = {
  title: 'React App',
  description: 'Web site created with Next.js.',
}

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

Con los cambios anteriores, pasaste de declarar todo en tu index.html a usar el enfoque basado en convenciones de Next.js integrado en el framework (API de Metadata). Este enfoque te permite mejorar más fácilmente el SEO y la capacidad de compartir tus páginas en la web.

Paso 5: Estilos

Al igual que CRA, Next.js admite CSS Modules de forma predeterminada. También admite importaciones de CSS global.

Si tienes un archivo CSS global, impórtalo en tu app/layout.tsx:

import '../index.css'

export const metadata = {
  title: 'React App',
  description: 'Web site created with Next.js.',
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>
        <div id="root">{children}</div>
      </body>
    </html>
  )
}

Si estás usando Tailwind CSS, consulta nuestra documentación de instalación.

Paso 6: Crear la página de entrada

Create React App usa src/index.tsx (o index.js) como punto de entrada. En Next.js (App Router), cada carpeta dentro del directorio app corresponde a una ruta, y cada carpeta debe tener un page.tsx.

Como queremos mantener la aplicación como una SPA por ahora e interceptar todas las rutas, usaremos una ruta de captura opcional.

  1. Crea un directorio [[...slug]] dentro de app.
app
 [[...slug]]
 page.tsx
 layout.tsx
  1. Agrega lo siguiente a page.tsx:
export function generateStaticParams() {
  return [{ slug: [''] }]
}

export default function Page() {
  return '...' // Actualizaremos esto
}
export function generateStaticParams() {
  return [{ slug: [''] }]
}

export default function Page() {
  return '...' // Actualizaremos esto
}

Esto le dice a Next.js que genere una sola ruta para el slug vacío (/), mapeando efectivamente todas las rutas a la misma página. Esta página es un Componente de Servidor, pre-renderizado en HTML estático.

Paso 7: Agregar un punto de entrada solo para cliente

A continuación, incrustaremos el componente raíz App de tu CRA dentro de un Componente de Cliente para que toda la lógica permanezca del lado del cliente. Si es la primera vez que usas Next.js, vale la pena saber que los componentes de cliente (por defecto) aún se pre-renderizan en el servidor. Puedes pensar en ellos como que tienen la capacidad adicional de ejecutar JavaScript del lado del cliente.

Crea un client.tsx (o client.js) en app/[[...slug]]/:

'use client'

import dynamic from 'next/dynamic'

const App = dynamic(() => import('../../App'), { ssr: false })

export function ClientOnly() {
  return <App />
}
'use client'

import dynamic from 'next/dynamic'

const App = dynamic(() => import('../../App'), { ssr: false })

export function ClientOnly() {
  return <App />
}
  • La directiva 'use client' hace que este archivo sea un Componente de Cliente.
  • La importación dynamic con ssr: false desactiva el renderizado del lado del servidor para el componente <App />, haciéndolo verdaderamente solo para cliente (SPA).

Ahora actualiza tu page.tsx (o page.js) para usar tu nuevo componente:

import { ClientOnly } from './client'

export function generateStaticParams() {
  return [{ slug: [''] }]
}

export default function Page() {
  return <ClientOnly />
}
import { ClientOnly } from './client'

export function generateStaticParams() {
  return [{ slug: [''] }]
}

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

Paso 8: Actualizar las importaciones de imágenes estáticas

En CRA, importar un archivo de imagen devuelve su URL pública como cadena:

import image from './img.png'

export default function App() {
  return <img src={image} />
}

Con Next.js, las importaciones de imágenes estáticas devuelven un objeto. Este objeto puede usarse directamente con el componente <Image> de Next.js, o puede usar la propiedad src del objeto con su etiqueta <img> existente.

El componente <Image> tiene los beneficios adicionales de optimización automática de imágenes. El componente <Image> establece automáticamente los atributos width y height del <img> resultante según las dimensiones de la imagen. Esto evita cambios de diseño cuando la imagen se carga. Sin embargo, esto puede causar problemas si su aplicación contiene imágenes con solo una de sus dimensiones estilizada sin que la otra esté configurada en auto. Cuando no está configurada en auto, la dimensión tomará por defecto el valor del atributo de dimensión del <img>, lo que puede hacer que la imagen aparezca distorsionada.

Mantener la etiqueta <img> reducirá la cantidad de cambios en su aplicación y evitará los problemas mencionados. Luego puede migrar opcionalmente al componente <Image> para aprovechar la optimización de imágenes configurando un loader, o migrando al servidor predeterminado de Next.js que tiene optimización automática de imágenes.

Convierta las rutas de importación absolutas para imágenes importadas desde /public en importaciones relativas:

// Antes
import logo from '/logo.png'

// Después
import logo from '../public/logo.png'

Pase la propiedad src de la imagen en lugar del objeto completo a su etiqueta <img>:

// Antes
<img src={logo} />

// Después
<img src={logo.src} />

Alternativamente, puede hacer referencia a la URL pública del recurso de imagen basado en el nombre del archivo. Por ejemplo, public/logo.png servirá la imagen en /logo.png para su aplicación, que sería el valor de src.

Advertencia: Si está usando TypeScript, puede encontrar errores de tipo al acceder a la propiedad src. Para solucionarlos, debe agregar next-env.d.ts al arreglo include de su archivo tsconfig.json. Next.js generará automáticamente este archivo cuando ejecute su aplicación en el paso 9.

Paso 9: Migrar variables de entorno

Next.js admite variables de entorno de manera similar a CRA, pero requiere el prefijo NEXT_PUBLIC_ para cualquier variable que desee exponer en el navegador.

La principal diferencia es el prefijo utilizado para exponer variables de entorno en el lado del cliente. Cambie todas las variables de entorno con el prefijo REACT_APP_ a NEXT_PUBLIC_.

Paso 10: Actualizar scripts en package.json

Actualice los scripts de su package.json para usar comandos de Next.js. También agregue .next y next-env.d.ts a su .gitignore:

package.json
{
  "scripts": {
    "dev": "next dev --turbopack",
    "build": "next build",
    "start": "npx serve@latest ./build"
  }
}
.gitignore
# ...
.next
next-env.d.ts

Ahora puede ejecutar:

npm run dev

Abra http://localhost:3000. Debería ver su aplicación ahora ejecutándose en Next.js (en modo SPA).

Paso 11: Limpieza

Ahora puede eliminar artefactos específicos de Create React App:

  • public/index.html
  • src/index.tsx
  • src/react-app-env.d.ts
  • La configuración de reportWebVitals
  • La dependencia react-scripts (desinstálela de package.json)

Consideraciones adicionales

Usar un homepage personalizado en CRA

Si utilizó el campo homepage en su package.json de CRA para servir la aplicación bajo una subruta específica, puede replicarlo en Next.js usando la configuración basePath en next.config.ts:

next.config.ts
import { NextConfig } from 'next'

const nextConfig: NextConfig = {
  basePath: '/my-subpath',
  // ...
}

export default nextConfig

Manejar un Service Worker personalizado

Si utilizó el service worker de CRA (por ejemplo, serviceWorker.js de create-react-app), puede aprender cómo crear Aplicaciones Web Progresivas (PWA) con Next.js.

Proxying de solicitudes API

Si su aplicación de CRA usó el campo proxy en package.json para redirigir solicitudes a un servidor backend, puede replicar esto con rewrites de Next.js en next.config.ts:

next.config.ts
import { NextConfig } from 'next'

const nextConfig: NextConfig = {
  async rewrites() {
    return [
      {
        source: '/api/:path*',
        destination: 'https://your-backend.com/:path*',
      },
    ]
  },
}

Configuración personalizada de Webpack / Babel

Si tenía una configuración personalizada de webpack o Babel en CRA, puede extender la configuración de Next.js en next.config.ts:

next.config.ts
import { NextConfig } from 'next'

const nextConfig: NextConfig = {
  webpack: (config, { isServer }) => {
    // Modifique la configuración de webpack aquí
    return config
  },
}

export default nextConfig

Nota: Esto requerirá desactivar Turbopack eliminando --turbopack de su script dev.

Configuración de TypeScript

Next.js configura automáticamente TypeScript si tiene un tsconfig.json. Asegúrese de que next-env.d.ts esté listado en el arreglo include de su tsconfig.json:

{
  "include": ["next-env.d.ts", "app/**/*", "src/**/*"]
}

Compatibilidad con el Bundler

Tanto Create React App como Next.js usan webpack por defecto para el bundling. Next.js también ofrece Turbopack para un desarrollo local más rápido con:

next dev --turbopack

Aún puede proporcionar una configuración personalizada de webpack si necesita migrar configuraciones avanzadas de webpack desde CRA.

Próximos pasos

Si todo funcionó, ahora tiene una aplicación Next.js funcional ejecutándose como una aplicación de una sola página. Aún no está aprovechando funciones de Next.js como el renderizado del lado del servidor (SSR) o el enrutamiento basado en archivos, pero ahora puede hacerlo incrementalmente:

Nota: Usar una exportación estática (output: 'export') actualmente no admite el hook useParams u otras funciones del servidor. Para usar todas las funciones de Next.js, elimine output: 'export' de su next.config.ts.