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:
- 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.
- 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 tupackage.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:
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.
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>
.
- Crea un nuevo directorio
app
dentro de tu carpetasrc
(o en la raíz de tu proyecto si prefieresapp
en la raíz). - Dentro del directorio
app
, crea un archivolayout.tsx
(olayout.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.
- Crea un directorio
[[...slug]]
dentro deapp
.
app
┣ [[...slug]]
┃ ┗ page.tsx
┣ layout.tsx
- 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
conssr: 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 agregarnext-env.d.ts
al arregloinclude
de su archivotsconfig.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
:
{
"scripts": {
"dev": "next dev --turbopack",
"build": "next build",
"start": "npx serve@latest ./build"
}
}
# ...
.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 depackage.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
:
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
:
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
:
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 scriptdev
.
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:
- Migre desde React Router al App Router de Next.js para:
- División de código automática
- Renderizado del lado del servidor (SSR) con streaming
- Componentes del lado del servidor (Server Components)
- Optimice imágenes con el componente
<Image>
- Optimice fuentes con
next/font
- Optimice scripts de terceros con el componente
<Script>
- Habilite ESLint con las reglas recomendadas de Next.js ejecutando
npx next lint
y configurándolo según las necesidades de su proyecto
Nota: Usar una exportación estática (
output: 'export'
) actualmente no admite el hookuseParams
u otras funciones del servidor. Para usar todas las funciones de Next.js, elimineoutput: 'export'
de sunext.config.ts
.