diff --git a/apps/docs/content/es/docs/01-app/01-getting-started/01-installation.mdx b/apps/docs/content/es/docs/01-app/01-getting-started/01-installation.mdx new file mode 100644 index 00000000..952b8201 --- /dev/null +++ b/apps/docs/content/es/docs/01-app/01-getting-started/01-installation.mdx @@ -0,0 +1,341 @@ +--- +source-updated-at: 2025-06-01T01:32:20.000Z +translation-updated-at: 2025-06-02T20:04:05.283Z +title: Cómo configurar un nuevo proyecto Next.js +nav_title: Instalación +description: Crea una nueva aplicación Next.js con la CLI `create-next-app` y configura TypeScript, ESLint y alias de rutas de módulos. +--- + +{/* El contenido de este documento se comparte entre el enrutador de aplicaciones y páginas. Puedes usar el componente `Contenido` para agregar contenido específico del Enrutador de Páginas. Cualquier contenido compartido no debe estar envuelto en un componente. */} + +## Requisitos del sistema + +Antes de comenzar, asegúrate de que tu sistema cumpla con los siguientes requisitos: + +- [Node.js 18.18](https://nodejs.org/) o superior. +- macOS, Windows (incluyendo WSL) o Linux. + +## Instalación automática + +La forma más rápida de crear una nueva aplicación Next.js es usando [`create-next-app`](/docs/app/api-reference/cli/create-next-app), que configura todo automáticamente. Para crear un proyecto, ejecuta: + +```bash filename="Terminal" +npx create-next-app@latest +``` + +Durante la instalación, verás los siguientes prompts: + +```txt filename="Terminal" +¿Cómo quieres llamar a tu proyecto? mi-app +¿Deseas usar TypeScript? No / Sí +¿Deseas usar ESLint? No / Sí +¿Deseas usar Tailwind CSS? No / Sí +¿Prefieres tu código dentro de un directorio `src/`? No / Sí +¿Deseas usar el Enrutador de Aplicación? (recomendado) No / Sí +¿Deseas usar Turbopack para `next dev`? No / Sí +¿Quieres personalizar el alias de importación (`@/*` por defecto)? No / Sí +¿Qué alias de importación deseas configurar? @/* +``` + +Después de los prompts, [`create-next-app`](/docs/app/api-reference/cli/create-next-app) creará una carpeta con el nombre de tu proyecto e instalará las dependencias necesarias. + +## Instalación manual + +Para crear manualmente una nueva aplicación Next.js, instala los paquetes requeridos: + +```bash filename="Terminal" +npm install next@latest react@latest react-dom@latest +``` + +Luego, agrega los siguientes scripts a tu archivo `package.json`: + +```json filename="package.json" +{ + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + } +} +``` + +Estos scripts corresponden a las diferentes etapas de desarrollo de una aplicación: + +- `next dev`: Inicia el servidor de desarrollo. +- `next build`: Construye la aplicación para producción. +- `next start`: Inicia el servidor de producción. +- `next lint`: Ejecuta ESLint. + + + +### Crear el directorio `app` + +Next.js usa enrutamiento basado en el sistema de archivos, lo que significa que las rutas en tu aplicación se determinan por cómo estructuras tus archivos. + +Crea una carpeta `app`. Luego, dentro de `app`, crea un archivo `layout.tsx`. Este archivo es el [layout raíz](/docs/app/api-reference/file-conventions/layout#root-layout). Es obligatorio y debe contener las etiquetas `` y ``. + +```tsx filename="app/layout.tsx" switcher +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + {children} + + ) +} +``` + +```jsx filename="app/layout.js" switcher +export default function RootLayout({ children }) { + return ( + + {children} + + ) +} +``` + +Crea una página de inicio `app/page.tsx` con contenido inicial: + +```tsx filename="app/page.tsx" switcher +export default function Page() { + return

¡Hola, Next.js!

+} +``` + +```jsx filename="app/page.js" switcher +export default function Page() { + return

¡Hola, Next.js!

+} +``` + +Tanto `layout.tsx` como `page.tsx` se renderizarán cuando el usuario visite la raíz de tu aplicación (`/`). + +Estructura de la carpeta App + +> **Nota importante**: +> +> - Si olvidas crear el layout raíz, Next.js creará automáticamente este archivo al ejecutar el servidor de desarrollo con `next dev`. +> - Opcionalmente puedes usar una [carpeta `src`](/docs/app/api-reference/file-conventions/src-folder) en la raíz de tu proyecto para separar el código de tu aplicación de los archivos de configuración. + +
+ + + +### Crear el directorio `pages` + +Next.js usa enrutamiento basado en el sistema de archivos, lo que significa que las rutas en tu aplicación se determinan por cómo estructuras tus archivos. + +Crea un directorio `pages` en la raíz de tu proyecto. Luego, agrega un archivo `index.tsx` dentro de tu carpeta `pages`. Esta será tu página de inicio (`/`): + +```tsx filename="pages/index.tsx" switcher +export default function Page() { + return

¡Hola, Next.js!

+} +``` + +```jsx filename="pages/index.js" switcher +export default function Page() { + return

¡Hola, Next.js!

+} +``` + +A continuación, agrega un archivo `_app.tsx` dentro de `pages/` para definir el layout global. Más información sobre el [archivo App personalizado](/docs/pages/building-your-application/routing/custom-app). + +```tsx filename="pages/_app.tsx" switcher +import type { AppProps } from 'next/app' + +export default function App({ Component, pageProps }: AppProps) { + return +} +``` + +```jsx filename="pages/_app.js" switcher +export default function App({ Component, pageProps }) { + return +} +``` + +Finalmente, agrega un archivo `_document.tsx` dentro de `pages/` para controlar la respuesta inicial del servidor. Más información sobre el [archivo Document personalizado](/docs/pages/building-your-application/routing/custom-document). + +```tsx filename="pages/_document.tsx" switcher +import { Html, Head, Main, NextScript } from 'next/document' + +export default function Document() { + return ( + + + +
+ + + + ) +} +``` + +```jsx filename="pages/_document.js" switcher +import { Html, Head, Main, NextScript } from 'next/document' + +export default function Document() { + return ( + + + +
+ + + + ) +} +``` + + + +### Crear la carpeta `public` (opcional) + +Crea una [carpeta `public`](/docs/app/api-reference/file-conventions/public-folder) en la raíz de tu proyecto para almacenar recursos estáticos como imágenes, fuentes, etc. Los archivos dentro de `public` pueden ser referenciados por tu código usando la URL base (`/`). + +Puedes referenciar estos recursos usando la ruta raíz (`/`). Por ejemplo, `public/profile.png` puede referenciarse como `/profile.png`: + +```tsx filename="app/page.tsx" highlight={4} switcher +import Image from 'next/image' + +export default function Page() { + return Perfil +} +``` + +```jsx filename="app/page.js" highlight={4} switcher +import Image from 'next/image' + +export default function Page() { + return Perfil +} +``` + +## Ejecutar el servidor de desarrollo + +1. Ejecuta `npm run dev` para iniciar el servidor de desarrollo. +2. Visita `http://localhost:3000` para ver tu aplicación. +3. Edita el archivo `app/page.tsx``pages/index.tsx` y guárdalo para ver los cambios actualizados en tu navegador. + +## Configurar TypeScript + +> Versión mínima de TypeScript: `v4.5.2` + +Next.js incluye soporte integrado para TypeScript. Para agregar TypeScript a tu proyecto, renombra un archivo a `.ts` / `.tsx` y ejecuta `next dev`. Next.js instalará automáticamente las dependencias necesarias y agregará un archivo `tsconfig.json` con las opciones de configuración recomendadas. + + + +### Plugin para IDE + +Next.js incluye un plugin personalizado de TypeScript y un verificador de tipos, que VSCode y otros editores de código pueden usar para verificación avanzada de tipos y autocompletado. + +Puedes habilitar el plugin en VS Code: + +1. Abriendo la paleta de comandos (`Ctrl/⌘` + `Shift` + `P`) +2. Buscando "TypeScript: Seleccionar versión de TypeScript" +3. Seleccionando "Usar versión del espacio de trabajo" + +Paleta de comandos de TypeScript + + + +Consulta la página de [referencia de TypeScript](/docs/app/api-reference/config/next-config-js/typescript) para más información. + +## Configurar ESLint + +Next.js incluye ESLint integrado. Instala automáticamente los paquetes necesarios y configura los ajustes adecuados cuando creas un nuevo proyecto con `create-next-app`. + +Para agregar ESLint manualmente a un proyecto existente, agrega `next lint` como script en `package.json`: + +```json filename="package.json" +{ + "scripts": { + "lint": "next lint" + } +} +``` + +Luego, ejecuta `npm run lint` y serás guiado a través del proceso de instalación y configuración. + +```bash filename="Terminal" +npm run lint +``` + +Verás un prompt como este: + +> ? ¿Cómo deseas configurar ESLint? +> +> ❯ Estricto (recomendado) +> Base +> Cancelar + +- **Estricto**: Incluye la configuración base de ESLint de Next.js junto con un conjunto de reglas más estrictas para Core Web Vitals. Esta es la configuración recomendada para desarrolladores que configuran ESLint por primera vez. +- **Base**: Incluye solo la configuración base de ESLint de Next.js. +- **Cancelar**: Omite la configuración. Selecciona esta opción si planeas configurar tu propia configuración personalizada de ESLint. + +Si seleccionas `Estricto` o `Base`, Next.js instalará automáticamente `eslint` y `eslint-config-next` como dependencias en tu aplicación y creará un archivo `.eslintrc.json` en la raíz de tu proyecto que incluirá tu configuración seleccionada. + +Ahora puedes ejecutar `next lint` cada vez que quieras ejecutar ESLint para detectar errores. Una vez configurado ESLint, también se ejecutará automáticamente durante cada construcción (`next build`). Los errores harán fallar la construcción, mientras que las advertencias no. + +Consulta la página del [Plugin ESLint](/docs/app/api-reference/config/next-config-js/eslint) para más información. + +## Configurar importaciones absolutas y alias de rutas de módulos + +Next.js tiene soporte integrado para las opciones `"paths"` y `"baseUrl"` de los archivos `tsconfig.json` y `jsconfig.json`. + +Estas opciones te permiten asignar alias a directorios del proyecto como rutas absolutas, haciendo más fácil y limpio importar módulos. Por ejemplo: + +```jsx +// Antes +import { Button } from '../../../components/button' + +// Después +import { Button } from '@/components/button' +``` + +Para configurar importaciones absolutas, agrega la opción `baseUrl` a tu archivo `tsconfig.json` o `jsconfig.json`. Por ejemplo: + +```json filename="tsconfig.json o jsconfig.json" +{ + "compilerOptions": { + "baseUrl": "src/" + } +} +``` + +Además de configurar la ruta `baseUrl`, puedes usar la opción `"paths"` para asignar alias a rutas de módulos. + +Por ejemplo, la siguiente configuración mapea `@/components/*` a `components/*`: + +```json filename="tsconfig.json o jsconfig.json" +{ + "compilerOptions": { + "baseUrl": "src/", + "paths": { + "@/styles/*": ["styles/*"], + "@/components/*": ["components/*"] + } + } +} +``` + +Cada una de las rutas `"paths"` es relativa a la ubicación `baseUrl`. \ No newline at end of file diff --git a/apps/docs/content/es/docs/01-app/01-getting-started/02-project-structure.mdx b/apps/docs/content/es/docs/01-app/01-getting-started/02-project-structure.mdx new file mode 100644 index 00000000..4cf1cea6 --- /dev/null +++ b/apps/docs/content/es/docs/01-app/01-getting-started/02-project-structure.mdx @@ -0,0 +1,408 @@ +--- +source-updated-at: 2025-06-01T01:32:20.000Z +translation-updated-at: 2025-06-02T20:05:44.569Z +title: Estructura y organización del proyecto +nav_title: Estructura del Proyecto +description: Una visión general de las convenciones de carpetas y archivos en Next.js, y cómo organizar tu proyecto. +--- + +Esta página proporciona una visión general de **todas** las convenciones de carpetas y archivos en Next.js, así como recomendaciones para organizar tu proyecto. + +## Convenciones de carpetas y archivos + +### Carpetas de nivel superior + +Las carpetas de nivel superior se utilizan para organizar el código y los recursos estáticos de tu aplicación. + +Segmentos de ruta a segmentos de path + +| | | +| ------------------------------------------------------------------ | ---------------------------------- | +| [`app`](/docs/app/building-your-application/routing) | Enrutador de la aplicación (App Router) | +| [`pages`](/docs/pages/building-your-application/routing) | Enrutador de páginas (Pages Router) | +| [`public`](/docs/app/api-reference/file-conventions/public-folder) | Recursos estáticos para servir | +| [`src`](/docs/app/api-reference/file-conventions/src-folder) | Carpeta opcional de código fuente | + +### Archivos de nivel superior + +Los archivos de nivel superior se utilizan para configurar tu aplicación, gestionar dependencias, ejecutar middleware, integrar herramientas de monitoreo y definir variables de entorno. + +| | | +| ---------------------------------------------------------------------------- | --------------------------------------- | +| **Next.js** | | +| [`next.config.js`](/docs/app/api-reference/config/next-config-js) | Archivo de configuración para Next.js | +| [`package.json`](/docs/app/getting-started/installation#manual-installation) | Dependencias y scripts del proyecto | +| [`instrumentation.ts`](/docs/app/guides/instrumentation) | Archivo de OpenTelemetry e Instrumentación | +| [`middleware.ts`](/docs/app/building-your-application/routing/middleware) | Middleware de solicitudes en Next.js | +| [`.env`](/docs/app/guides/environment-variables) | Variables de entorno | +| [`.env.local`](/docs/app/guides/environment-variables) | Variables de entorno locales | +| [`.env.production`](/docs/app/guides/environment-variables) | Variables de entorno de producción | +| [`.env.development`](/docs/app/guides/environment-variables) | Variables de entorno de desarrollo | +| [`.eslintrc.json`](/docs/app/api-reference/config/eslint) | Archivo de configuración para ESLint | +| `.gitignore` | Archivos y carpetas ignorados por Git | +| `next-env.d.ts` | Archivo de declaración TypeScript para Next.js | +| `tsconfig.json` | Archivo de configuración para TypeScript | +| `jsconfig.json` | Archivo de configuración para JavaScript | + + + +### Archivos de enrutamiento + +| | | | +| ----------------------------------------------------------------------------- | ------------------- | ---------------------------- | +| [`layout`](/docs/app/api-reference/file-conventions/layout) | `.js` `.jsx` `.tsx` | Diseño (Layout) | +| [`page`](/docs/app/api-reference/file-conventions/page) | `.js` `.jsx` `.tsx` | Página | +| [`loading`](/docs/app/api-reference/file-conventions/loading) | `.js` `.jsx` `.tsx` | Interfaz de carga | +| [`not-found`](/docs/app/api-reference/file-conventions/not-found) | `.js` `.jsx` `.tsx` | Interfaz de no encontrado | +| [`error`](/docs/app/api-reference/file-conventions/error) | `.js` `.jsx` `.tsx` | Interfaz de error | +| [`global-error`](/docs/app/api-reference/file-conventions/error#global-error) | `.js` `.jsx` `.tsx` | Interfaz de error global | +| [`route`](/docs/app/api-reference/file-conventions/route) | `.js` `.ts` | Endpoint API | +| [`template`](/docs/app/api-reference/file-conventions/template) | `.js` `.jsx` `.tsx` | Diseño re-renderizado | +| [`default`](/docs/app/api-reference/file-conventions/default) | `.js` `.jsx` `.tsx` | Página de respaldo para rutas paralelas | + +### Rutas anidadas + +| | | +| --------------- | -------------------- | +| `carpeta` | Segmento de ruta | +| `carpeta/carpeta` | Segmento de ruta anidado | + +### Rutas dinámicas + +| | | +| ------------------------------------------------------------------------------------------------------ | -------------------------------- | +| [`[carpeta]`](/docs/app/api-reference/file-conventions/dynamic-routes#convention) | Segmento de ruta dinámica | +| [`[...carpeta]`](/docs/app/api-reference/file-conventions/dynamic-routes#catch-all-segments) | Segmento de ruta catch-all | +| [`[[...carpeta]]`](/docs/app/api-reference/file-conventions/dynamic-routes#optional-catch-all-segments) | Segmento de ruta catch-all opcional | + +### Grupos de rutas y carpetas privadas + +| | | +| ------------------------------------------------------------------------------ | ------------------------------------------------ | +| [`(carpeta)`](/docs/app/api-reference/file-conventions/route-groups#convention) | Agrupar rutas sin afectar el enrutamiento | +| [`_carpeta`](#private-folders) | Excluir carpeta y segmentos hijos del enrutamiento | + +### Rutas paralelas e interceptadas + +| | | +| ------------------------------------------------------------------------------------------- | -------------------------- | +| [`@carpeta`](/docs/app/api-reference/file-conventions/parallel-routes#slots) | Ranura nombrada | +| [`(.)carpeta`](/docs/app/api-reference/file-conventions/intercepting-routes#convention) | Interceptar mismo nivel | +| [`(..)carpeta`](/docs/app/api-reference/file-conventions/intercepting-routes#convention) | Interceptar un nivel arriba | +| [`(..)(..)carpeta`](/docs/app/api-reference/file-conventions/intercepting-routes#convention) | Interceptar dos niveles arriba | +| [`(...)carpeta`](/docs/app/api-reference/file-conventions/intercepting-routes#convention) | Interceptar desde la raíz | + +### Convenciones de archivos de metadatos + +#### Iconos de la aplicación + +| | | | +| --------------------------------------------------------------------------------------------------------------- | ----------------------------------- | ------------------------ | +| [`favicon`](/docs/app/api-reference/file-conventions/metadata/app-icons#favicon) | `.ico` | Archivo favicon | +| [`icon`](/docs/app/api-reference/file-conventions/metadata/app-icons#icon) | `.ico` `.jpg` `.jpeg` `.png` `.svg` | Archivo de icono de app | +| [`icon`](/docs/app/api-reference/file-conventions/metadata/app-icons#generate-icons-using-code-js-ts-tsx) | `.js` `.ts` `.tsx` | Icono de app generado | +| [`apple-icon`](/docs/app/api-reference/file-conventions/metadata/app-icons#apple-icon) | `.jpg` `.jpeg`, `.png` | Archivo de icono Apple | +| [`apple-icon`](/docs/app/api-reference/file-conventions/metadata/app-icons#generate-icons-using-code-js-ts-tsx) | `.js` `.ts` `.tsx` | Icono Apple generado | + +#### Imágenes Open Graph y Twitter + +| | | | +| --------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | -------------------------- | +| [`opengraph-image`](/docs/app/api-reference/file-conventions/metadata/opengraph-image#opengraph-image) | `.jpg` `.jpeg` `.png` `.gif` | Archivo imagen Open Graph | +| [`opengraph-image`](/docs/app/api-reference/file-conventions/metadata/opengraph-image#generate-images-using-code-js-ts-tsx) | `.js` `.ts` `.tsx` | Imagen Open Graph generada | +| [`twitter-image`](/docs/app/api-reference/file-conventions/metadata/opengraph-image#twitter-image) | `.jpg` `.jpeg` `.png` `.gif` | Archivo imagen Twitter | +| [`twitter-image`](/docs/app/api-reference/file-conventions/metadata/opengraph-image#generate-images-using-code-js-ts-tsx) | `.js` `.ts` `.tsx` | Imagen Twitter generada | + +#### SEO + +| | | | +| ------------------------------------------------------------------------------------------------------------ | ----------- | --------------------- | +| [`sitemap`](/docs/app/api-reference/file-conventions/metadata/sitemap#sitemap-files-xml) | `.xml` | Archivo sitemap | +| [`sitemap`](/docs/app/api-reference/file-conventions/metadata/sitemap#generating-a-sitemap-using-code-js-ts) | `.js` `.ts` | Sitemap generado | +| [`robots`](/docs/app/api-reference/file-conventions/metadata/robots#static-robotstxt) | `.txt` | Archivo robots | +| [`robots`](/docs/app/api-reference/file-conventions/metadata/robots#generate-a-robots-file) | `.js` `.ts` | Archivo robots generado | + + + + + +### Convenciones de archivos + +| | | | +| ----------------------------------------------------------------------------------------------------------- | ------------------- | ----------------- | +| [`_app`](/docs/pages/building-your-application/routing/custom-app) | `.js` `.jsx` `.tsx` | App personalizada | +| [`_document`](/docs/pages/building-your-application/routing/custom-document) | `.js` `.jsx` `.tsx` | Documento personalizado | +| [`_error`](/docs/pages/building-your-application/routing/custom-error#more-advanced-error-page-customizing) | `.js` `.jsx` `.tsx` | Página de error personalizada | +| [`404`](/docs/pages/building-your-application/routing/custom-error#404-page) | `.js` `.jsx` `.tsx` | Página de error 404 | +| [`500`](/docs/pages/building-your-application/routing/custom-error#500-page) | `.js` `.jsx` `.tsx` | Página de error 500 | + +### Rutas + +| | | | +| ---------------------------------------------------------------------------------------------- | ------------------- | ----------- | +| **Convención de carpetas** | | | +| [`index`](/docs/pages/building-your-application/routing/pages-and-layouts#index-routes) | `.js` `.jsx` `.tsx` | Página principal | +| [`carpeta/index`](/docs/pages/building-your-application/routing/pages-and-layouts#index-routes) | `.js` `.jsx` `.tsx` | Página anidada | +| **Convención de archivos** | | | +| [`index`](/docs/pages/building-your-application/routing/pages-and-layouts#index-routes) | `.js` `.jsx` `.tsx` | Página principal | +| [`archivo`](/docs/pages/building-your-application/routing/pages-and-layouts) | `.js` `.jsx` `.tsx` | Página anidada | + +### Rutas dinámicas + +| | | | +| ----------------------------------------------------------------------------------------------------------------- | ------------------- | -------------------------------- | +| **Convención de carpetas** | | | +| [`[carpeta]/index`](/docs/pages/building-your-application/routing/dynamic-routes) | `.js` `.jsx` `.tsx` | Segmento de ruta dinámica | +| [`[...carpeta]/index`](/docs/pages/building-your-application/routing/dynamic-routes#catch-all-segments) | `.js` `.jsx` `.tsx` | Segmento de ruta catch-all | +| [`[[...carpeta]]/index`](/docs/pages/building-your-application/routing/dynamic-routes#optional-catch-all-segments) | `.js` `.jsx` `.tsx` | Segmento de ruta catch-all opcional | +| **Convención de archivos** | | | +| [`[archivo]`](/docs/pages/building-your-application/routing/dynamic-routes) | `.js` `.jsx` `.tsx` | Segmento de ruta dinámica | +| [`[...archivo]`](/docs/pages/building-your-application/routing/dynamic-routes#catch-all-segments) | `.js` `.jsx` `.tsx` | Segmento de ruta catch-all | +| [`[[...archivo]]`](/docs/pages/building-your-application/routing/dynamic-routes#optional-catch-all-segments) | `.js` `.jsx` `.tsx` | Segmento de ruta catch-all opcional | + + + + + +## Organizando tu proyecto + +Next.js es **no-opinionado** sobre cómo organizas y colocas los archivos de tu proyecto. Pero sí proporciona varias características para ayudarte a organizarlo. + +### Jerarquía de componentes + +Los componentes definidos en archivos especiales se renderizan en una jerarquía específica: + +- `layout.js` +- `template.js` +- `error.js` (límite de error de React) +- `loading.js` (límite de suspense de React) +- `not-found.js` (límite de error de React) +- `page.js` o `layout.js` anidado + +Jerarquía de Componentes para Convenciones de Archivos + +Los componentes se renderizan recursivamente en rutas anidadas, lo que significa que los componentes de un segmento de ruta estarán anidados **dentro** de los componentes de su segmento padre. + +Jerarquía de Componentes Anidados para Convenciones de Archivos + + + +### Colocación + +En el directorio `app`, las carpetas anidadas definen la estructura de rutas. Cada carpeta representa un segmento de ruta que se mapea a un segmento correspondiente en una ruta URL. + +Sin embargo, aunque la estructura de rutas se define mediante carpetas, una ruta **no es accesible públicamente** hasta que se agrega un archivo `page.js` o `route.js` a un segmento de ruta. + +Un diagrama que muestra cómo una ruta no es accesible públicamente hasta que se agrega un archivo page.js o route.js a un segmento de ruta. + +Y, incluso cuando una ruta se hace accesible públicamente, solo el **contenido devuelto** por `page.js` o `route.js` se envía al cliente. + +Un diagrama que muestra cómo los archivos page.js y route.js hacen que las rutas sean accesibles públicamente. + +Esto significa que los **archivos del proyecto** pueden **colocarse de forma segura** dentro de segmentos de ruta en el directorio `app` sin que accidentalmente sean accesibles como rutas. + +Un diagrama que muestra que los archivos del proyecto colocados no son accesibles como rutas, incluso cuando un segmento contiene un archivo page.js o route.js. + +> **Es bueno saberlo**: Aunque **puedes** colocar tus archivos de proyecto en `app`, no **tienes** que hacerlo. Si lo prefieres, puedes [mantenerlos fuera del directorio `app`](#store-project-files-outside-of-app). + +### Carpetas privadas + +Las carpetas privadas se pueden crear anteponiendo un guión bajo al nombre de la carpeta: `_nombreDeCarpeta`. + +Esto indica que la carpeta es un detalle de implementación privado y no debe ser considerado por el sistema de enrutamiento, **excluyendo la carpeta y todas sus subcarpetas** del enrutamiento. + +Un ejemplo de estructura de carpetas usando carpetas privadas + +Dado que los archivos en el directorio `app` pueden [colocarse de forma segura por defecto](#colocation), las carpetas privadas no son necesarias para la colocación. Sin embargo, pueden ser útiles para: + +- Separar la lógica de la interfaz de usuario de la lógica de enrutamiento. +- Organizar de manera consistente los archivos internos en un proyecto y en el ecosistema de Next.js. +- Clasificar y agrupar archivos en editores de código. +- Evitar posibles conflictos de nombres con futuras convenciones de archivos de Next.js. + +> **Es bueno saberlo**: +> +> - Aunque no es una convención del framework, también podrías considerar marcar archivos fuera de carpetas privadas como "privados" usando el mismo patrón de guión bajo. +> - Puedes crear segmentos URL que comiencen con un guión bajo anteponiendo `%5F` (la forma codificada en URL de un guión bajo) al nombre de la carpeta: `%5FnombreDeCarpeta`. +> - Si no usas carpetas privadas, sería útil conocer las [convenciones de archivos especiales](/docs/app/getting-started/project-structure#routing-files) de Next.js para evitar conflictos de nombres inesperados. + +### Grupos de rutas + +Los grupos de rutas se pueden crear envolviendo una carpeta entre paréntesis: `(nombreDeCarpeta)` + +Esto indica que la carpeta es para fines organizativos y **no debe incluirse** en la ruta URL. + +Un ejemplo de estructura de carpetas usando grupos de rutas + +Los grupos de rutas son útiles para: + +- Organizar rutas por sección del sitio, intención o equipo. Ej: páginas de marketing, páginas de administración, etc. +- Habilitar diseños anidados en el mismo nivel de segmento de ruta: + - [Crear múltiples diseños anidados en el mismo segmento, incluyendo múltiples diseños raíz](#creating-multiple-root-layouts) + - [Agregar un diseño a un subconjunto de rutas en un segmento común](#opting-specific-segments-into-a-layout) + +### Carpeta `src` + +Next.js admite almacenar el código de la aplicación (incluyendo `app`) dentro de una carpeta [`src` opcional](/docs/app/api-reference/file-conventions/src-folder). Esto separa el código de la aplicación de los archivos de configuración del proyecto que principalmente residen en la raíz del proyecto. + +Un ejemplo de estructura de carpetas con la carpeta `src` + +## Ejemplos + +La siguiente sección enumera un resumen de alto nivel de estrategias comunes. La conclusión más simple es elegir una estrategia que funcione para ti y tu equipo y ser consistente en todo el proyecto. + +> **Es bueno saberlo**: En nuestros ejemplos a continuación, estamos usando las carpetas `components` y `lib` como marcadores de posición generalizados, sus nombres no tienen un significado especial en el framework y tus proyectos podrían usar otras carpetas como `ui`, `utils`, `hooks`, `styles`, etc. + +### Almacenar archivos de proyecto fuera de `app` + +Esta estrategia almacena todo el código de la aplicación en carpetas compartidas en la **raíz de tu proyecto** y mantiene el directorio `app` puramente para fines de enrutamiento. + +Un ejemplo de estructura de carpetas con archivos de proyecto fuera de app + +### Almacenar archivos de proyecto en carpetas de nivel superior dentro de `app` + +Esta estrategia almacena todo el código de la aplicación en carpetas compartidas en la **raíz del directorio `app`**. + +Un ejemplo de estructura de carpetas con archivos de proyecto dentro de app + +### Dividir archivos de proyecto por funcionalidad o ruta + +Esta estrategia almacena el código de la aplicación compartido globalmente en la raíz del directorio `app` y **divide** el código de la aplicación más específico en los segmentos de ruta que lo utilizan. + +Un ejemplo de estructura de carpetas con archivos de proyecto divididos por funcionalidad o ruta + +### Organizar rutas sin afectar la ruta URL + +Para organizar rutas sin afectar la URL, crea un grupo para mantener juntas las rutas relacionadas. Las carpetas entre paréntesis se omitirán de la URL (ej: `(marketing)` o `(shop)`). + +Organización de rutas con grupos de rutas + +Aunque las rutas dentro de `(marketing)` y `(shop)` comparten la misma jerarquía URL, puedes crear un diseño diferente para cada grupo agregando un archivo `layout.js` dentro de sus carpetas. + +Grupos de rutas con múltiples diseños + +### Optar por diseños en segmentos específicos + +Para incluir rutas específicas en un diseño, crea un nuevo grupo de rutas (ej: `(shop)`) y mueve las rutas que comparten el mismo diseño al grupo (ej: `account` y `cart`). Las rutas fuera del grupo no compartirán el diseño (ej: `checkout`). + +Grupos de rutas con diseños opt-in + +### Optar por skeletons de carga en una ruta específica + +Para aplicar un [skeleton de carga](/docs/app/building-your-application/routing/loading-ui-and-streaming) mediante un archivo `loading.js` a una ruta específica, crea un nuevo grupo de rutas (ej: `/(overview)`) y luego mueve tu `loading.tsx` dentro de ese grupo de rutas. + +Estructura de carpetas mostrando un loading.tsx y un page.tsx dentro del grupo de rutas + +Ahora, el archivo `loading.tsx` solo se aplicará a tu página dashboard → overview en lugar de a todas tus páginas dashboard, sin afectar la estructura de la ruta URL. + +### Crear múltiples diseños raíz + +Para crear múltiples [diseños raíz](/docs/app/api-reference/file-conventions/layout#root-layout), elimina el archivo `layout.js` de nivel superior y agrega un archivo `layout.js` dentro de cada grupo de rutas. Esto es útil para dividir una aplicación en secciones que tienen una interfaz o experiencia completamente diferente. Las etiquetas `` y `` deben agregarse a cada diseño raíz. + +Grupos de rutas con múltiples diseños raíz + +En el ejemplo anterior, tanto `(marketing)` como `(shop)` tienen su propio diseño raíz. + + diff --git a/apps/docs/content/es/docs/01-app/01-getting-started/03-layouts-and-pages.mdx b/apps/docs/content/es/docs/01-app/01-getting-started/03-layouts-and-pages.mdx new file mode 100644 index 00000000..32e8b754 --- /dev/null +++ b/apps/docs/content/es/docs/01-app/01-getting-started/03-layouts-and-pages.mdx @@ -0,0 +1,294 @@ +--- +source-updated-at: 2025-06-01T01:32:20.000Z +translation-updated-at: 2025-06-02T20:03:35.044Z +title: Cómo crear diseños y páginas +nav_title: Diseños y Páginas +description: Crea tus primeras páginas y diseños, y enlázalos entre sí. +related: + title: Referencia de API + description: Aprende más sobre las características mencionadas en esta página leyendo la Referencia de API. + links: + - app/api-reference/file-conventions/layout + - app/api-reference/file-conventions/page + - app/api-reference/components/link + - app/api-reference/file-conventions/dynamic-routes +--- + +Next.js utiliza **enrutamiento basado en el sistema de archivos**, lo que significa que puedes usar carpetas y archivos para definir rutas. Esta página te guiará sobre cómo crear diseños y páginas, y enlazarlos entre sí. + +## Creando una página + +Una **página** es una interfaz de usuario que se renderiza en una ruta específica. Para crear una página, añade un [archivo `page`](/docs/app/api-reference/file-conventions/page) dentro del directorio `app` y exporta por defecto un componente React. Por ejemplo, para crear una página de inicio (`/`): + +page.js special file + +```tsx filename="app/page.tsx" switcher +export default function Page() { + return

Hello Next.js!

+} +``` + +```jsx filename="app/page.js" switcher +export default function Page() { + return

Hello Next.js!

+} +``` + +## Creando un diseño + +Un diseño es una interfaz de usuario que se **comparte** entre múltiples páginas. Durante la navegación, los diseños preservan el estado, permanecen interactivos y no se vuelven a renderizar. + +Puedes definir un diseño exportando por defecto un componente React desde un [archivo `layout`](/docs/app/api-reference/file-conventions/layout). El componente debe aceptar una prop `children` que puede ser una página u otro [diseño anidado](#anidando-diseños). + +Por ejemplo, para crear un diseño que acepte tu página de inicio como hijo, añade un archivo `layout` dentro del directorio `app`: + +layout.js special file + +```tsx filename="app/layout.tsx" switcher +export default function DashboardLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + + {/* UI del diseño */} + {/* Coloca children donde quieras renderizar una página o diseño anidado */} +
{children}
+ + + ) +} +``` + +```jsx filename="app/layout.js" switcher +export default function DashboardLayout({ children }) { + return ( + + + {/* UI del diseño */} + {/* Coloca children donde quieras renderizar una página o diseño anidado */} +
{children}
+ + + ) +} +``` + +El diseño anterior se llama [diseño raíz](/docs/app/api-reference/file-conventions/layout#root-layout) porque está definido en la raíz del directorio `app`. El diseño raíz es **obligatorio** y debe contener las etiquetas `html` y `body`. + +## Creando una ruta anidada + +Una ruta anidada es una ruta compuesta por múltiples segmentos de URL. Por ejemplo, la ruta `/blog/[slug]` está compuesta por tres segmentos: + +- `/` (Segmento raíz) +- `blog` (Segmento) +- `[slug]` (Segmento hoja) + +En Next.js: + +- Las **carpetas** se usan para definir los segmentos de ruta que mapean a segmentos de URL. +- Los **archivos** (como `page` y `layout`) se usan para crear la interfaz de usuario que se muestra para un segmento. + +Para crear rutas anidadas, puedes anidar carpetas dentro de otras. Por ejemplo, para añadir una ruta para `/blog`, crea una carpeta llamada `blog` en el directorio `app`. Luego, para hacer `/blog` públicamente accesible, añade un archivo `page.tsx`: + +File hierarchy showing blog folder and a page.js file + +```tsx filename="app/blog/page.tsx" switcher +// Importaciones de ejemplo +import { getPosts } from '@/lib/posts' +import { Post } from '@/ui/post' + +export default async function Page() { + const posts = await getPosts() + + return ( +
    + {posts.map((post) => ( + + ))} +
+ ) +} +``` + +```jsx filename="app/blog/[slug]/page.js" switcher +// Importaciones de ejemplo +import { getPosts } from '@/lib/posts' +import { Post } from '@/ui/post' + +export default async function Page() { + const posts = await getPosts() + + return ( +
    + {posts.map((post) => ( + + ))} +
+ ) +} +``` + +Puedes continuar anidando carpetas para crear rutas anidadas. Por ejemplo, para crear una ruta para una publicación de blog específica, crea una nueva carpeta `[slug]` dentro de `blog` y añade un archivo `page`: + +File hierarchy showing blog folder with a nested slug folder and a page.js file + +```tsx filename="app/blog/[slug]/page.tsx" switcher +function generateStaticParams() {} + +export default function Page() { + return

Hello, Blog Post Page!

+} +``` + +```jsx filename="app/blog/[slug]/page.js" switcher +function generateStaticParams() {} + +export default function Page() { + return

Hello, Blog Post Page!

+} +``` + +Envolver un nombre de carpeta en corchetes (ej. `[slug]`) crea un [segmento de ruta dinámico](/docs/app/api-reference/file-conventions/dynamic-routes) que se usa para generar múltiples páginas desde datos. Ej. publicaciones de blog, páginas de productos, etc. + +## Anidando diseños + +Por defecto, los diseños en la jerarquía de carpetas también están anidados, lo que significa que envuelven diseños hijos a través de su prop `children`. Puedes anidar diseños añadiendo `layout` dentro de segmentos de ruta específicos (carpetas). + +Por ejemplo, para crear un diseño para la ruta `/blog`, añade un nuevo archivo `layout` dentro de la carpeta `blog`. + +File hierarchy showing root layout wrapping the blog layout + +```tsx filename="app/blog/layout.tsx" switcher +export default function BlogLayout({ + children, +}: { + children: React.ReactNode +}) { + return
{children}
+} +``` + +```jsx filename="app/blog/layout.js" switcher +export default function BlogLayout({ children }) { + return
{children}
+} +``` + +Si combinaras los dos diseños anteriores, el diseño raíz (`app/layout.js`) envolvería el diseño del blog (`app/blog/layout.js`), que a su vez envolvería la página del blog (`app/blog/page.js`) y la página de publicación (`app/blog/[slug]/page.js`). + +## Creando un segmento dinámico + +Los [segmentos dinámicos](/docs/app/api-reference/file-conventions/dynamic-routes) te permiten crear rutas generadas desde datos. Por ejemplo, en lugar de crear manualmente una ruta para cada publicación de blog, puedes crear un segmento dinámico para generar las rutas basadas en los datos de las publicaciones. + +Para crear un segmento dinámico, envuelve el nombre del segmento (carpeta) en corchetes: `[segmentName]`. Por ejemplo, en la ruta `app/blog/[slug]/page.tsx`, `[slug]` es el segmento dinámico. + +```tsx filename="app/blog/[slug]/page.tsx" switcher +export default async function BlogPostPage({ + params, +}: { + params: Promise<{ slug: string }> +}) { + const { slug } = await params + const post = await getPost(slug) + + return ( +
+

{post.title}

+

{post.content}

+
+ ) +} +``` + +```jsx filename="app/blog/[slug]/page.js" switcher +export default async function BlogPostPage({ params }) { + const { slug } = await params + const post = await getPost(slug) + + return ( +
+

{post.title}

+

{post.content}

+
+ ) +} +``` + +Aprende más sobre [Segmentos Dinámicos](/docs/app/api-reference/file-conventions/dynamic-routes). + +## Enlazando entre páginas + +Puedes usar el [componente ``](/docs/app/api-reference/components/link) para navegar entre rutas. `` es un componente integrado de Next.js que extiende la etiqueta HTML `` para proporcionar [precarga](/docs/app/building-your-application/routing/linking-and-navigating#2-prefetching) y [navegación del lado del cliente](/docs/app/building-your-application/routing/linking-and-navigating#5-soft-navigation). + +Por ejemplo, para generar una lista de publicaciones de blog, importa `` desde `next/link` y pasa una prop `href` al componente: + +```tsx filename="app/ui/post.tsx" highlight={1,10} switcher +import Link from 'next/link' + +export default async function Post({ post }) { + const posts = await getPosts() + + return ( +
    + {posts.map((post) => ( +
  • + {post.title} +
  • + ))} +
+ ) +} +``` + +```jsx filename="app/ui/post.js" highlight={1,10} switcher +import Link from 'next/link' + +export default async function Post({ post }) { + const posts = await getPosts() + + return ( +
    + {posts.map((post) => ( +
  • + {post.title} +
  • + ))} +
+ ) +} +``` + +`` es la forma principal y recomendada para navegar entre rutas en tu aplicación Next.js. Sin embargo, también puedes usar el [hook `useRouter`](/docs/app/api-reference/functions/use-router) para navegación más avanzada. \ No newline at end of file diff --git a/apps/docs/content/es/docs/01-app/01-getting-started/04-images.mdx b/apps/docs/content/es/docs/01-app/01-getting-started/04-images.mdx new file mode 100644 index 00000000..6f60daa2 --- /dev/null +++ b/apps/docs/content/es/docs/01-app/01-getting-started/04-images.mdx @@ -0,0 +1,199 @@ +--- +source-updated-at: 2025-06-02T15:30:01.000Z +translation-updated-at: 2025-06-02T20:02:45.834Z +title: Cómo optimizar imágenes +nav_title: Imágenes +description: Aprenda a optimizar imágenes en Next.js +related: + title: Referencia de API + description: Consulte la Referencia de API para ver todas las funciones de Next.js Image. + links: + - app/api-reference/components/image +--- + +El componente [``](/docs/app/api-reference/components/image) de Next.js extiende el elemento HTML `` para proporcionar: + +- **Optimización de tamaño:** Sirve automáticamente imágenes del tamaño correcto para cada dispositivo, usando formatos modernos como WebP. +- **Estabilidad visual:** Previene automáticamente el [desplazamiento de diseño (layout shift)](https://web.dev/articles/cls) mientras las imágenes se cargan. +- **Carga más rápida de páginas:** Solo carga imágenes cuando entran en el viewport usando carga diferida (lazy loading) nativa del navegador, con placeholders opcionales de desenfoque (blur-up). +- **Flexibilidad de recursos:** Redimensiona imágenes bajo demanda, incluso imágenes almacenadas en servidores remotos. + +Para comenzar a usar ``, impórtelo desde `next/image` y renderícelo dentro de su componente. + +```tsx filename="app/page.tsx" switcher +import Image from 'next/image' + +export default function Page() { + return +} +``` + +```jsx filename="app/page.js" switcher +import Image from 'next/image' + +export default function Page() { + return +} +``` + +La propiedad `src` puede ser una imagen [local](#local-images) o [remota](#remote-images). + +> **🎥 Video:** Aprenda más sobre cómo usar `next/image` → [YouTube (9 minutos)](https://youtu.be/IU_qq_c_lKA). + +## Imágenes locales + +Puede almacenar archivos estáticos, como imágenes y fuentes, en una carpeta llamada [`public`](/docs/app/api-reference/file-conventions/public-folder) en el directorio raíz. Los archivos dentro de `public` pueden ser referenciados por su código comenzando desde la URL base (`/`). + +Estructura de carpetas mostrando las carpetas app y public + +```tsx filename="app/page.tsx" switcher +import Image from 'next/image' + +export default function Page() { + return ( + Foto del autor + ) +} +``` + +```jsx filename="app/page.js" switcher +import Image from 'next/image' + +export default function Page() { + return ( + Foto del autor + ) +} +``` + +### Imágenes importadas estáticamente + +También puede importar y usar archivos de imagen locales. Next.js determinará automáticamente el [`width`](/docs/app/api-reference/components/image#width-and-height) y [`height`](/docs/app/api-reference/components/image#width-and-height) intrínsecos de su imagen basándose en el archivo importado. Estos valores se usan para determinar la proporción de la imagen y prevenir [Desplazamiento Acumulativo de Diseño (Cumulative Layout Shift)](https://web.dev/articles/cls) mientras su imagen se carga. + +```tsx filename="app/page.tsx" switcher +import Image from 'next/image' +import ProfileImage from './profile.png' + +export default function Page() { + return ( + Foto del autor + ) +} +``` + +```jsx filename="app/page.js" switcher +import Image from 'next/image' +import ProfileImage from './profile.png' + +export default function Page() { + return ( + Foto del autor + ) +} +``` + +En este caso, Next.js espera que el archivo `app/profile.png` esté disponible. + +## Imágenes remotas + +Para usar una imagen remota, puede proporcionar una cadena URL para la propiedad `src`. + +```tsx filename="app/page.tsx" switcher +import Image from 'next/image' + +export default function Page() { + return ( + Foto del autor + ) +} +``` + +```jsx filename="app/page.js" switcher +import Image from 'next/image' + +export default function Page() { + return ( + Foto del autor + ) +} +``` + +Dado que Next.js no tiene acceso a archivos remotos durante el proceso de construcción, deberá proporcionar manualmente las propiedades [`width`](/docs/app/api-reference/components/image#width-and-height), [`height`](/docs/app/api-reference/components/image#width-and-height) y opcionalmente [`blurDataURL`](/docs/app/api-reference/components/image#blurdataurl). El `width` y `height` se usan para inferir la proporción correcta de la imagen y evitar desplazamientos de diseño al cargar la imagen. + +Para permitir de forma segura imágenes de servidores remotos, debe definir una lista de patrones de URL admitidos en [`next.config.js`](/docs/app/api-reference/config/next-config-js). Sea lo más específico posible para prevenir usos maliciosos. Por ejemplo, la siguiente configuración solo permitirá imágenes de un bucket específico de AWS S3: + +```ts filename="next.config.ts" switcher +import type { NextConfig } from 'next' + +const config: NextConfig = { + images: { + remotePatterns: [ + { + protocol: 'https', + hostname: 's3.amazonaws.com', + port: '', + pathname: '/my-bucket/**', + search: '', + }, + ], + }, +} + +export default config +``` + +```js filename="next.config.js" switcher +module.exports = { + images: { + remotePatterns: [ + { + protocol: 'https', + hostname: 's3.amazonaws.com', + port: '', + pathname: '/my-bucket/**', + search: '', + }, + ], + }, +} +``` \ No newline at end of file diff --git a/apps/docs/content/es/docs/01-app/01-getting-started/05-fonts.mdx b/apps/docs/content/es/docs/01-app/01-getting-started/05-fonts.mdx new file mode 100644 index 00000000..2cbaf6c8 --- /dev/null +++ b/apps/docs/content/es/docs/01-app/01-getting-started/05-fonts.mdx @@ -0,0 +1,203 @@ +--- +source-updated-at: 2025-06-01T01:32:20.000Z +translation-updated-at: 2025-06-02T20:02:27.959Z +title: Cómo usar fuentes +nav_title: Fuentes +description: Aprenda a utilizar fuentes en Next.js +related: + title: Referencia de API + description: Consulte la Referencia de API para el conjunto completo de características de Next.js Font + links: + - app/api-reference/components/font +--- + +El módulo [`next/font`](/docs/app/api-reference/components/font) optimiza automáticamente sus fuentes y elimina solicitudes de red externas para mejorar la privacidad y el rendimiento. + +Incluye **auto-hospedaje integrado** para cualquier archivo de fuente. Esto significa que puede cargar fuentes web de manera óptima sin cambios de diseño (layout shift). + +Para comenzar a usar `next/font`, impórtelo desde [`next/font/local`](#local-fonts) o [`next/font/google`](#google-fonts), llámelo como una función con las opciones apropiadas y establezca el `className` del elemento al que desea aplicar la fuente. Por ejemplo: + +```tsx filename="app/layout.tsx" highlight={1,3-5,9} switcher +import { Geist } from 'next/font/google' + +const geist = Geist({ + subsets: ['latin'], +}) + +export default function Layout({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ) +} +``` + +```jsx filename="app/layout.js" highlight={1,3-5,9} switcher +import { Geist } from 'next/font/google' + +const geist = Geist({ + subsets: ['latin'], +}) + +export default function Layout({ children }) { + return ( + + {children} + + ) +} +``` + +Las fuentes están limitadas al componente en el que se utilizan. Para aplicar una fuente a toda su aplicación, agréguela al [Layout Raíz](/docs/app/api-reference/file-conventions/layout#root-layout). + +## Fuentes de Google + +Puede auto-hospedar automáticamente cualquier fuente de Google. Las fuentes se incluyen como activos estáticos y se sirven desde el mismo dominio que su implementación, lo que significa que el navegador no envía solicitudes a Google cuando el usuario visita su sitio. + +Para comenzar a usar una fuente de Google, importe la fuente elegida desde `next/font/google`: + +```tsx filename="app/layout.tsx" switcher +import { Geist } from 'next/font/google' + +const geist = Geist({ + subsets: ['latin'], +}) + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + {children} + + ) +} +``` + +```jsx filename="app/layout.js" switcher +import { Geist } from 'next/font/google' + +const geist = Geist({ + subsets: ['latin'], +}) + +export default function RootLayout({ children }) { + return ( + + {children} + + ) +} +``` + +Recomendamos usar [fuentes variables](https://fonts.google.com/variablefonts) para obtener el mejor rendimiento y flexibilidad. Pero si no puede usar una fuente variable, deberá especificar un peso: + +```tsx filename="app/layout.tsx" highlight={4} switcher +import { Roboto } from 'next/font/google' + +const roboto = Roboto({ + weight: '400', + subsets: ['latin'], +}) + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + {children} + + ) +} +``` + +```jsx filename="app/layout.js" highlight={4} switcher +import { Roboto } from 'next/font/google' + +const roboto = Roboto({ + weight: '400', + subsets: ['latin'], +}) + +export default function RootLayout({ children }) { + return ( + + {children} + + ) +} +``` + +## Fuentes locales + +Para usar una fuente local, importe su fuente desde `next/font/local` y especifique el [`src`](/docs/app/api-reference/components/font#src) de su archivo de fuente local. Las fuentes se pueden almacenar en la carpeta [`public`](/docs/app/api-reference/file-conventions/public-folder). Por ejemplo: + +```tsx filename="app/layout.tsx" switcher +import localFont from 'next/font/local' + +const myFont = localFont({ + src: './my-font.woff2', +}) + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + {children} + + ) +} +``` + +```jsx filename="app/layout.js" switcher +import localFont from 'next/font/local' + +const myFont = localFont({ + src: './my-font.woff2', +}) + +export default function RootLayout({ children }) { + return ( + + {children} + + ) +} +``` + +Si desea usar múltiples archivos para una sola familia de fuentes, `src` puede ser un array: + +```js +const roboto = localFont({ + src: [ + { + path: './Roboto-Regular.woff2', + weight: '400', + style: 'normal', + }, + { + path: './Roboto-Italic.woff2', + weight: '400', + style: 'italic', + }, + { + path: './Roboto-Bold.woff2', + weight: '700', + style: 'normal', + }, + { + path: './Roboto-BoldItalic.woff2', + weight: '700', + style: 'italic', + }, + ], +}) +``` \ No newline at end of file diff --git a/apps/docs/content/es/docs/01-app/01-getting-started/06-css.mdx b/apps/docs/content/es/docs/01-app/01-getting-started/06-css.mdx new file mode 100644 index 00000000..20ce88be --- /dev/null +++ b/apps/docs/content/es/docs/01-app/01-getting-started/06-css.mdx @@ -0,0 +1,295 @@ +--- +source-updated-at: 2025-05-25T15:16:02.000Z +translation-updated-at: 2025-06-02T20:03:03.214Z +title: Cómo usar CSS en tu aplicación +nav_title: CSS +description: Conoce las diferentes formas de agregar CSS a tu aplicación, incluyendo Módulos CSS, CSS Global, Tailwind CSS y más. +related: + title: Próximos pasos + description: Aprende más sobre las alternativas para usar CSS en tu aplicación. + links: + - app/guides/tailwind-css + - app/guides/sass + - app/guides/css-in-js +--- + +Next.js ofrece varias formas de usar CSS en tu aplicación, incluyendo: + +- [Módulos CSS](#css-modules) +- [CSS Global](#global-css) +- [Hojas de estilo externas](#external-stylesheets) +- [Tailwind CSS](/docs/app/guides/tailwind-css) +- [Sass](/docs/app/guides/sass) +- [CSS-in-JS](/docs/app/guides/css-in-js) + +## Módulos CSS + +Los Módulos CSS (CSS Modules) limitan el alcance del CSS localmente generando nombres de clase únicos. Esto te permite usar la misma clase en diferentes archivos sin preocuparte por colisiones de nombres. + + + +Para empezar a usar Módulos CSS, crea un nuevo archivo con la extensión `.module.css` e impórtalo en cualquier componente dentro del directorio `app`: + +```css filename="app/blog/blog.module.css" +.blog { + padding: 24px; +} +``` + +```tsx filename="app/blog/page.tsx" switcher +import styles from './blog.module.css' + +export default function Page() { + return
+} +``` + +```jsx filename="app/blog/page.js" switcher +import styles from './blog.module.css' + +export default function Layout() { + return
+} +``` + +
+ + + +Para empezar a usar Módulos CSS, crea un nuevo archivo con la extensión `.module.css` e impórtalo en cualquier componente dentro del directorio `pages`: + +```css filename="/styles/blog.module.css" +.blog { + padding: 24px; +} +``` + +```tsx filename="pages/blog/index.tsx" switcher +import styles from './blog.module.css' + +export default function Page() { + return
+} +``` + +```jsx filename="pages/blog/index.js" switcher +import styles from './blog.module.css' + +export default function Page() { + return
+} +``` + +
+ +## CSS Global + +Puedes usar CSS global para aplicar estilos en toda tu aplicación. + + + +Crea un archivo `app/global.css` e impórtalo en el layout raíz para aplicar los estilos a **cada ruta** de tu aplicación: + +```css filename="app/global.css" +body { + padding: 20px 20px 60px; + max-width: 680px; + margin: 0 auto; +} +``` + +```tsx filename="app/layout.tsx" switcher +// Estos estilos se aplican a cada ruta en la aplicación +import './global.css' + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + {children} + + ) +} +``` + +```jsx filename="app/layout.js" switcher +// Estos estilos se aplican a cada ruta en la aplicación +import './global.css' + +export default function RootLayout({ children }) { + return ( + + {children} + + ) +} +``` + +> **Nota importante:** Los estilos globales pueden importarse en cualquier layout, página o componente dentro del directorio `app`. Sin embargo, como Next.js usa el soporte incorporado de React para hojas de estilo e integración con Suspense, actualmente no elimina las hojas de estilo al navegar entre rutas, lo que puede causar conflictos. Recomendamos usar estilos globales solo para CSS _verdaderamente_ global, y [Módulos CSS](#css-modules) para CSS con alcance limitado. + + + + + +Importa la hoja de estilo en el archivo `pages/_app.js` para aplicar los estilos a **cada ruta** en tu aplicación: + +```tsx filename="pages/_app.js" +import '@/styles/global.css' + +export default function MyApp({ Component, pageProps }) { + return +} +``` + +Debido al alcance global de las hojas de estilo, y para evitar conflictos, debes importarlas dentro de [`pages/_app.js`](/docs/pages/building-your-application/routing/custom-app). + + + +## Hojas de estilo externas + + + +Las hojas de estilo publicadas por paquetes externos pueden importarse en cualquier lugar del directorio `app`, incluyendo componentes colocalizados: + +```tsx filename="app/layout.tsx" switcher +import 'bootstrap/dist/css/bootstrap.css' + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + {children} + + ) +} +``` + +```jsx filename="app/layout.js" switcher +import 'bootstrap/dist/css/bootstrap.css' + +export default function RootLayout({ children }) { + return ( + + {children} + + ) +} +``` + +> **Nota importante:** En React 19, también se puede usar ``. Consulta la [documentación de `link` en React](https://react.dev/reference/react-dom/components/link) para más información. + + + + + +Next.js te permite importar archivos CSS desde un archivo JavaScript. +Esto es posible porque Next.js extiende el concepto de [`import`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/import) más allá de JavaScript. + +### Importar estilos desde `node_modules` + +Desde Next.js **9.5.4**, se permite importar archivos CSS desde `node_modules` en cualquier parte de tu aplicación. + +Para hojas de estilo globales, como `bootstrap` o `nprogress`, debes importar el archivo dentro de `pages/_app.js`. Por ejemplo: + +```jsx filename="pages/_app.js" +import 'bootstrap/dist/css/bootstrap.css' + +export default function MyApp({ Component, pageProps }) { + return +} +``` + +Para importar CSS requerido por un componente de terceros, puedes hacerlo en tu componente. Por ejemplo: + +```jsx filename="components/example-dialog.js" +import { useState } from 'react' +import { Dialog } from '@reach/dialog' +import VisuallyHidden from '@reach/visually-hidden' +import '@reach/dialog/styles.css' + +function ExampleDialog(props) { + const [showDialog, setShowDialog] = useState(false) + const open = () => setShowDialog(true) + const close = () => setShowDialog(false) + + return ( +
+ + + +

Hola. Soy un diálogo

+
+
+ ) +} +``` + +
+ +## Orden y fusión + +Next.js optimiza CSS durante las compilaciones de producción fusionando automáticamente las hojas de estilo. El **orden de tu CSS** depende del **orden en que importas los estilos en tu código**. + +Por ejemplo, `base-button.module.css` se ordenará antes que `page.module.css` ya que `` se importa antes que `page.module.css`: + +```tsx filename="page.ts" switcher +import { BaseButton } from './base-button' +import styles from './page.module.css' + +export default function Page() { + return +} +``` + +```jsx filename="page.js" switcher +import { BaseButton } from './base-button' +import styles from './page.module.css' + +export default function Page() { + return +} +``` + +```tsx filename="base-button.tsx" switcher +import styles from './base-button.module.css' + +export function BaseButton() { + return + + ) +} +``` + +```jsx filename="app/ui/counter.tsx" highlight={1} switcher +'use client' + +import { useState } from 'react' + +export default function Counter() { + const [count, setCount] = useState(0) + + return ( +
+

{count} likes

+ +
+ ) +} +``` + +`"use client"` se usa para declarar un **límite** entre los grafos (árboles) de módulos de Servidor y Cliente. + +Una vez que un archivo está marcado con `"use client"`, **todos sus imports y componentes hijos se consideran parte del paquete del cliente**. Esto significa que no necesitas añadir la directiva a cada componente destinado al cliente. + +### Reduciendo el tamaño del paquete JS + +Para reducir el tamaño de tus paquetes de JavaScript del cliente, añade `'use client'` a componentes interactivos específicos en lugar de marcar grandes partes de tu UI como Componentes de Cliente. + +Por ejemplo, el componente `` contiene principalmente elementos estáticos como un logo y enlaces de navegación, pero incluye una barra de búsqueda interactiva. `` es interactivo y necesita ser un Componente de Cliente, sin embargo, el resto del diseño puede permanecer como un Componente de Servidor. + +```tsx filename="app/ui/search.tsx" highlight={1} switcher +'use client' + +export default function Search() { + // ... +} +``` + +```jsx filename="app/ui/search.js" highlight={1} switcher +'use client' + +export default function Search() { + // ... +} +``` + +```tsx filename="app/layout.tsx" switcher +// Componente de Cliente +import Search from './search' +// Componente de Servidor +import Logo from './logo' + +// Layout es un Componente de Servidor por defecto +export default function Layout({ children }: { children: React.ReactNode }) { + return ( + <> + +
{children}
+ + ) +} +``` + +```jsx filename="app/layout.js" switcher +// Componente de Cliente +import Search from './search' +// Componente de Servidor +import Logo from './logo' + +// Layout es un Componente de Servidor por defecto +export default function Layout({ children }) { + return ( + <> + +
{children}
+ + ) +} +``` + +### Pasando datos de Componentes de Servidor a Cliente + +Puedes pasar datos de Componentes de Servidor a Componentes de Cliente usando props. + +```tsx filename="app/[id]/page.tsx" highlight={1,7} switcher +import LikeButton from '@/app/ui/like-button' +import { getPost } from '@/lib/data' + +export default async function Page({ params }: { params: { id: string } }) { + const post = await getPost(params.id) + + return +} +``` + +```jsx filename="app/[id]/page.js" highlight={1,7} switcher +import LikeButton from '@/app/ui/like-button' +import { getPost } from '@/lib/data' + +export default async function Page({ params }) { + const post = await getPost(params.id) + + return +} +``` + +```tsx filename="app/ui/like-button.tsx" highlight={1} switcher +'use client' + +export default function LikeButton({ likes }: { likes: number }) { + // ... +} +``` + +```jsx filename="app/ui/like-button.js" highlight={1} switcher +'use client' + +export default function LikeButton({ likes }) { + // ... +} +``` + +Alternativamente, puedes transmitir datos de un Componente de Servidor a un Componente de Cliente con el [Hook `use`](https://react.dev/reference/react/use). Consulta un [ejemplo](/docs/app/getting-started/fetching-data#streaming-data-with-the-use-hook). + +> **Nota importante**: Las props pasadas a Componentes de Cliente deben ser [serializables](https://react.dev/reference/react/use-server#serializable-parameters-and-return-values) por React. + +### Intercalando Componentes de Servidor y Cliente + +Puedes pasar Componentes de Servidor como prop a un Componente de Cliente. Esto te permite anidar visualmente UI renderizada en el servidor dentro de componentes del cliente. + +Un patrón común es usar `children` para crear un _slot_ en un ``. Por ejemplo, un componente `` que obtiene datos en el servidor, dentro de un componente `` que usa estado del cliente para alternar la visibilidad. + +```tsx filename="app/ui/modal.tsx" switcher +'use client' + +export default function Modal({ children }: { children: React.ReactNode }) { + return
{children}
+} +``` + +```jsx filename="app/ui/modal.js" switcher +'use client' + +export default function Modal({ children }) { + return
{children}
+} +``` + +Luego, en un Componente de Servidor padre (ej. ``), puedes pasar un `` como hijo del ``: + +```tsx filename="app/page.tsx" highlight={7} switcher +import Modal from './ui/modal' +import Cart from './ui/cart' + +export default function Page() { + return ( + + + + ) +} +``` + +```jsx filename="app/page.js" highlight={7} switcher +import Modal from './ui/modal' +import Cart from './ui/cart' + +export default function Page() { + return ( + + + + ) +} +``` + +En este patrón, todos los Componentes de Servidor se renderizarán en el servidor con anticipación, incluyendo aquellos pasados como props. El RSC Payload resultante contendrá referencias de dónde deben renderizarse los Componentes de Cliente dentro del árbol de componentes. + +### Proveedores de contexto + +[El contexto de React](https://react.dev/learn/passing-data-deeply-with-context) se usa comúnmente para compartir estado global como el tema actual. Sin embargo, el contexto de React no es compatible con Componentes de Servidor. + +Para usar contexto, crea un Componente de Cliente que acepte `children`: + +```tsx filename="app/theme-provider.tsx" switcher +'use client' + +import { createContext } from 'react' + +export const ThemeContext = createContext({}) + +export default function ThemeProvider({ + children, +}: { + children: React.ReactNode +}) { + return {children} +} +``` + +```jsx filename="app/theme-provider.js" switcher +'use client' + +import { createContext } from 'react' + +export const ThemeContext = createContext({}) + +export default function ThemeProvider({ children }) { + return {children} +} +``` + +Luego, impórtalo en un Componente de Servidor (ej. `layout`): + +```tsx filename="app/layout.tsx" switcher +import ThemeProvider from './theme-provider' + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + + {children} + + + ) +} +``` + +```jsx filename="app/layout.js" switcher +import ThemeProvider from './theme-provider' + +export default function RootLayout({ children }) { + return ( + + + {children} + + + ) +} +``` + +Tu Componente de Servidor ahora podrá renderizar directamente tu proveedor, y todos los demás Componentes de Cliente en tu aplicación podrán consumir este contexto. + +> **Nota importante**: Debes renderizar los proveedores lo más profundo posible en el árbol – observa cómo `ThemeProvider` solo envuelve `{children}` en lugar de todo el documento ``. Esto facilita que Next.js optimice las partes estáticas de tus Componentes de Servidor. + +### Componentes de terceros + +Cuando uses un componente de terceros que dependa de características exclusivas del cliente, puedes envolverlo en un Componente de Cliente para asegurarte de que funcione como se espera. + +Por ejemplo, el `` puede importarse del paquete `acme-carousel`. Este componente usa `useState`, pero aún no tiene la directiva `"use client"`. + +Si usas `` dentro de un Componente de Cliente, funcionará como se espera: + +```tsx filename="app/gallery.tsx" switcher +'use client' + +import { useState } from 'react' +import { Carousel } from 'acme-carousel' + +export default function Gallery() { + const [isOpen, setIsOpen] = useState(false) + + return ( +
+ + {/* Funciona, ya que Carousel se usa dentro de un Componente de Cliente */} + {isOpen && } +
+ ) +} +``` + +```jsx filename="app/gallery.js" switcher +'use client' + +import { useState } from 'react' +import { Carousel } from 'acme-carousel' + +export default function Gallery() { + const [isOpen, setIsOpen] = useState(false) + + return ( +
+ + {/* Funciona, ya que Carousel se usa dentro de un Componente de Cliente */} + {isOpen && } +
+ ) +} +``` + +Sin embargo, si intentas usarlo directamente dentro de un Componente de Servidor, verás un error. Esto se debe a que Next.js no sabe que `` usa características exclusivas del cliente. + +Para solucionarlo, puedes envolver componentes de terceros que dependan de características exclusivas del cliente en tus propios Componentes de Cliente: + +```tsx filename="app/carousel.tsx" switcher +'use client' + +import { Carousel } from 'acme-carousel' + +export default Carousel +``` + +```jsx filename="app/carousel.js" switcher +'use client' + +import { Carousel } from 'acme-carousel' + +export default Carousel +``` + +Ahora, puedes usar `` directamente dentro de un Componente de Servidor: + +```tsx filename="app/page.tsx" switcher +import Carousel from './carousel' + +export default function Page() { + return ( +
+

Ver imágenes

+ {/* Funciona, ya que Carousel es un Componente de Cliente */} + +
+ ) +} +``` + +```jsx filename="app/page.js" switcher +import Carousel from './carousel' + +export default function Page() { + return ( +
+

Ver imágenes

+ {/* Funciona, ya que Carousel es un Componente de Cliente */} + +
+ ) +} +``` + +> **Consejo para autores de bibliotecas** +> +> Si estás construyendo una biblioteca de componentes, añade la directiva `"use client"` a los puntos de entrada que dependan de características exclusivas del cliente. Esto permite que tus usuarios importen componentes en Componentes de Servidor sin necesidad de crear wrappers. +> +> Vale la pena mencionar que algunos empaquetadores podrían eliminar las directivas `"use client"`. Puedes encontrar un ejemplo de cómo configurar esbuild para incluir la directiva `"use client"` en los repositorios [React Wrap Balancer](https://github.com/shuding/react-wrap-balancer/blob/main/tsup.config.ts#L10-L13) y [Vercel Analytics](https://github.com/vercel/analytics/blob/main/packages/web/tsup.config.js#L26-L30). + +### Prevención de envenenamiento del entorno (environment poisoning) + +Los módulos de JavaScript pueden compartirse entre módulos de Componentes del Servidor (Server Components) y del Cliente (Client Components). Esto significa que es posible importar accidentalmente código exclusivo del servidor en el cliente. Por ejemplo, considere la siguiente función: + +```ts filename="lib/data.ts" switcher +export async function getData() { + const res = await fetch('https://external-service.com/data', { + headers: { + authorization: process.env.API_KEY, + }, + }) + + return res.json() +} +``` + +```js filename="lib/data.js" switcher +export async function getData() { + const res = await fetch('https://external-service.com/data', { + headers: { + authorization: process.env.API_KEY, + }, + }) + + return res.json() +} +``` + +Esta función contiene una `API_KEY` que nunca debería exponerse al cliente. + +En Next.js, solo las variables de entorno con el prefijo `NEXT_PUBLIC_` se incluyen en el paquete del cliente. Si las variables no tienen este prefijo, Next.js las reemplaza con una cadena vacía. + +Como resultado, aunque `getData()` puede importarse y ejecutarse en el cliente, no funcionará como se espera. + +Para prevenir el uso accidental en Componentes del Cliente (Client Components), puede utilizar el paquete [`server-only`](https://www.npmjs.com/package/server-only). + +```bash filename="Terminal" +npm install server-only +``` + +Luego, importe el paquete en un archivo que contenga código exclusivo del servidor: + +```js filename="lib/data.js" +import 'server-only' + +export async function getData() { + const res = await fetch('https://external-service.com/data', { + headers: { + authorization: process.env.API_KEY, + }, + }) + + return res.json() +} +``` + +Ahora, si intenta importar este módulo en un Componente del Cliente (Client Component), se producirá un error en tiempo de compilación. + +> **Nota importante**: El paquete correspondiente [`client-only`](https://www.npmjs.com/package/client-only) puede utilizarse para marcar módulos que contienen lógica exclusiva del cliente, como código que accede al objeto `window`. diff --git a/apps/docs/content/es/docs/01-app/01-getting-started/08-fetching-data.mdx b/apps/docs/content/es/docs/01-app/01-getting-started/08-fetching-data.mdx new file mode 100644 index 00000000..ad9239d0 --- /dev/null +++ b/apps/docs/content/es/docs/01-app/01-getting-started/08-fetching-data.mdx @@ -0,0 +1,658 @@ +--- +source-updated-at: 2025-06-01T01:32:20.000Z +translation-updated-at: 2025-06-02T20:05:16.495Z +title: Cómo obtener datos y transmitirlos en flujo (streaming) +nav_title: Obtención de datos +description: Comience a obtener datos y transmitir contenido en su aplicación. +related: + title: Referencia de API + description: Aprenda más sobre las características mencionadas en esta página leyendo la Referencia de API. + links: + - app/api-reference/functions/fetch + - app/api-reference/file-conventions/loading + - app/api-reference/config/next-config-js/logging + - app/api-reference/config/next-config-js/taint +--- + +Esta página le guiará sobre cómo puede obtener datos en [Componentes de Servidor y Cliente](/docs/app/getting-started/server-and-client-components), y cómo [transmitir en flujo](#streaming) componentes que dependen de datos. + +## Obtención de datos + +### Componentes de Servidor + +Puede obtener datos en Componentes de Servidor usando: + +1. La [API `fetch`](#con-la-api-fetch) +2. Un [ORM o base de datos](#con-un-orm-o-base-de-datos) + +#### Con la API `fetch` + +Para obtener datos con la API `fetch`, convierta su componente en una función asíncrona y espere (`await`) la llamada a `fetch`. Por ejemplo: + +```tsx filename="app/blog/page.tsx" switcher +export default async function Page() { + const data = await fetch('https://api.vercel.app/blog') + const posts = await data.json() + return ( +
    + {posts.map((post) => ( +
  • {post.title}
  • + ))} +
+ ) +} +``` + +```jsx filename="app/blog/page.js" switcher +export default async function Page() { + const data = await fetch('https://api.vercel.app/blog') + const posts = await data.json() + return ( +
    + {posts.map((post) => ( +
  • {post.title}
  • + ))} +
+ ) +} +``` + +> **Nota importante:** +> +> - Las respuestas de `fetch` no se almacenan en caché por defecto. Sin embargo, Next.js [prerrenderizará](/docs/app/getting-started/partial-prerendering#static-rendering) la ruta y la salida se almacenará en caché para mejorar el rendimiento. Si desea optar por el [renderizado dinámico](/docs/app/getting-started/partial-prerendering#dynamic-rendering), use la opción `{ cache: 'no-store' }`. Consulte la [Referencia de la API `fetch`](/docs/app/api-reference/functions/fetch). +> - Durante el desarrollo, puede registrar las llamadas a `fetch` para una mejor visibilidad y depuración. Consulte la [referencia de API `logging`](/docs/app/api-reference/config/next-config-js/logging). + +#### Con un ORM o base de datos + +Dado que los Componentes de Servidor se renderizan en el servidor, puede realizar consultas a la base de datos de manera segura usando un ORM o cliente de base de datos. Convierta su componente en una función asíncrona y espere (`await`) la llamada: + +```tsx filename="app/blog/page.tsx" switcher +import { db, posts } from '@/lib/db' + +export default async function Page() { + const allPosts = await db.select().from(posts) + return ( +
    + {allPosts.map((post) => ( +
  • {post.title}
  • + ))} +
+ ) +} +``` + +```jsx filename="app/blog/page.js" switcher +import { db, posts } from '@/lib/db' + +export default async function Page() { + const allPosts = await db.select().from(posts) + return ( +
    + {allPosts.map((post) => ( +
  • {post.title}
  • + ))} +
+ ) +} +``` + +### Componentes de Cliente + +Hay dos formas de obtener datos en Componentes de Cliente, usando: + +1. El [hook `use` de React](https://react.dev/reference/react/use) +2. Una biblioteca de la comunidad como [SWR](https://swr.vercel.app/) o [React Query](https://tanstack.com/query/latest) + +#### Transmisión de datos con el hook `use` + +Puede usar el [hook `use` de React](https://react.dev/reference/react/use) para [transmitir en flujo](#streaming) datos desde el servidor al cliente. Comience obteniendo datos en su componente de Servidor y pase la promesa a su Componente de Cliente como prop: + +```tsx filename="app/blog/page.tsx" switcher +import Posts from '@/app/ui/posts +import { Suspense } from 'react' + +export default function Page() { + // No espere la función de obtención de datos + const posts = getPosts() + + return ( + Loading...}> + + + ) +} +``` + +```jsx filename="app/blog/page.js" switcher +import Posts from '@/app/ui/posts +import { Suspense } from 'react' + +export default function Page() { + // No espere la función de obtención de datos + const posts = getPosts() + + return ( + Loading...}> + + + ) +} +``` + +Luego, en su Componente de Cliente, use el hook `use` para leer la promesa: + +```tsx filename="app/ui/posts.tsx" switcher +'use client' +import { use } from 'react' + +export default function Posts({ + posts, +}: { + posts: Promise<{ id: string; title: string }[]> +}) { + const allPosts = use(posts) + + return ( +
    + {allPosts.map((post) => ( +
  • {post.title}
  • + ))} +
+ ) +} +``` + +```jsx filename="app/ui/posts.js" switcher +'use client' +import { use } from 'react' + +export default function Posts({ posts }) { + const posts = use(posts) + + return ( +
    + {posts.map((post) => ( +
  • {post.title}
  • + ))} +
+ ) +} +``` + +En el ejemplo anterior, el componente `` está envuelto en un límite [``](https://react.dev/reference/react/Suspense). Esto significa que se mostrará el fallback mientras se resuelve la promesa. Aprenda más sobre [transmisión en flujo](#streaming). + +#### Bibliotecas de la comunidad + +Puede usar una biblioteca de la comunidad como [SWR](https://swr.vercel.app/) o [React Query](https://tanstack.com/query/latest) para obtener datos en Componentes de Cliente. Estas bibliotecas tienen sus propias semánticas para almacenamiento en caché, transmisión en flujo y otras características. Por ejemplo, con SWR: + +```tsx filename="app/blog/page.tsx" switcher +'use client' +import useSWR from 'swr' + +const fetcher = (url) => fetch(url).then((r) => r.json()) + +export default function BlogPage() { + const { data, error, isLoading } = useSWR( + 'https://api.vercel.app/blog', + fetcher + ) + + if (isLoading) return
Loading...
+ if (error) return
Error: {error.message}
+ + return ( +
    + {data.map((post: { id: string; title: string }) => ( +
  • {post.title}
  • + ))} +
+ ) +} +``` + +```jsx filename="app/blog/page.js" switcher +'use client' + +import useSWR from 'swr' + +const fetcher = (url) => fetch(url).then((r) => r.json()) + +export default function BlogPage() { + const { data, error, isLoading } = useSWR( + 'https://api.vercel.app/blog', + fetcher + ) + + if (isLoading) return
Loading...
+ if (error) return
Error: {error.message}
+ + return ( +
    + {data.map((post) => ( +
  • {post.title}
  • + ))} +
+ ) +} +``` + +## Eliminación de duplicados de solicitudes con `React.cache` + +La eliminación de duplicados es el proceso de _evitar solicitudes duplicadas_ para el mismo recurso durante un pase de renderizado. Le permite obtener los mismos datos en diferentes componentes mientras evita múltiples solicitudes de red a su fuente de datos. + +Si está usando `fetch`, las solicitudes pueden eliminarse duplicados agregando `cache: 'force-cache'`. Esto significa que puede llamar de manera segura a la misma URL con las mismas opciones, y solo se hará una solicitud. + +Si _no_ está usando `fetch`, y en su lugar usa un ORM o base de datos directamente, puede envolver su obtención de datos con la función [React `cache`](https://react.dev/reference/react/cache). + +```tsx filename="app/lib/data.ts" switcher +import { cache } from 'react' +import { db, posts, eq } from '@/lib/db' + +export const getPost = cache(async (id: string) => { + const post = await db.query.posts.findFirst({ + where: eq(posts.id, parseInt(id)), + }) +}) +``` + +```jsx filename="app/lib/data.js" switcher +import { cache } from 'react' +import { db, posts, eq } from '@/lib/db' +import { notFound } from 'next/navigation' + +export const getPost = cache(async (id) => { + const post = await db.query.posts.findFirst({ + where: eq(posts.id, parseInt(id)), + }) +}) +``` + +## Transmisión en flujo (Streaming) + +> **Advertencia:** El contenido a continuación asume que la opción de configuración [`dynamicIO`](/docs/app/api-reference/config/next-config-js/dynamicIO) está habilitada en su aplicación. Esta bandera se introdujo en Next.js 15 canary. + +Al usar `async/await` en Componentes de Servidor, Next.js optará por el [renderizado dinámico](/docs/app/getting-started/partial-prerendering#dynamic-rendering). Esto significa que los datos se obtendrán y renderizarán en el servidor para cada solicitud de usuario. Si hay alguna solicitud de datos lenta, toda la ruta se bloqueará para renderizar. + +Para mejorar el tiempo de carga inicial y la experiencia del usuario, puede usar la transmisión en flujo para dividir el HTML de la página en fragmentos más pequeños y enviar progresivamente esos fragmentos desde el servidor al cliente. + +Cómo funciona el renderizado del servidor con transmisión en flujo + +Hay dos formas en las que puede implementar la transmisión en flujo en su aplicación: + +1. Envolviendo una página con un [archivo `loading.js`](#con-loadingjs) +2. Envolviendo un componente con [``](#con-suspense) + +### Con `loading.js` + +Puede crear un archivo `loading.js` en la misma carpeta que su página para transmitir en flujo **toda la página** mientras se obtienen los datos. Por ejemplo, para transmitir `app/blog/page.js`, agregue el archivo dentro de la carpeta `app/blog`. + +Estructura de carpeta de Blog con archivo loading.js + +```tsx filename="app/blog/loading.tsx" switcher +export default function Loading() { + // Defina la UI de carga aquí + return
Loading...
+} +``` + +```jsx filename="app/blog/loading.js" switcher +export default function Loading() { + // Defina la UI de carga aquí + return
Loading...
+} +``` + +Al navegar, el usuario verá inmediatamente el diseño y un [estado de carga](#creando-estados-de-carga-significativos) mientras se renderiza la página. El nuevo contenido se intercambiará automáticamente una vez que se complete el renderizado. + +UI de carga + +Detrás de escena, `loading.js` se anidará dentro de `layout.js` y envolverá automáticamente el archivo `page.js` y cualquier hijo en un límite ``. + +Resumen de loading.js + +Este enfoque funciona bien para segmentos de ruta (diseños y páginas), pero para una transmisión en flujo más granular, puede usar ``. + +### Con `` + +`` le permite ser más granular sobre qué partes de la página transmitir en flujo. Por ejemplo, puede mostrar inmediatamente cualquier contenido de página que esté fuera del límite ``, y transmitir en flujo la lista de publicaciones del blog dentro del límite. + +```tsx filename="app/blog/page.tsx" switcher +import { Suspense } from 'react' +import BlogList from '@/components/BlogList' +import BlogListSkeleton from '@/components/BlogListSkeleton' + +export default function BlogPage() { + return ( +
+ {/* Este contenido se enviará al cliente inmediatamente */} +
+

Bienvenido al Blog

+

Lea las últimas publicaciones a continuación.

+
+
+ {/* Cualquier contenido envuelto en un límite se transmitirá en flujo */} + }> + + +
+
+ ) +} +``` + +```jsx filename="app/blog/page.js" switcher +import { Suspense } from 'react' +import BlogList from '@/components/BlogList' +import BlogListSkeleton from '@/components/BlogListSkeleton' + +export default function BlogPage() { + return ( +
+ {/* Este contenido se enviará al cliente inmediatamente */} +
+

Bienvenido al Blog

+

Lea las últimas publicaciones a continuación.

+
+
+ {/* Cualquier contenido envuelto en un límite se transmitirá en flujo */} + }> + + +
+
+ ) +} +``` + +### Creando estados de carga significativos + +Un estado de carga instantáneo es una UI de respaldo que se muestra inmediatamente al usuario después de la navegación. Para la mejor experiencia de usuario, recomendamos diseñar estados de carga que sean significativos y ayuden a los usuarios a entender que la aplicación está respondiendo. Por ejemplo, puede usar esqueletos y spinners, o una parte pequeña pero significativa de las pantallas futuras, como una foto de portada, título, etc. + +En desarrollo, puede previsualizar e inspeccionar el estado de carga de sus componentes usando las [React Devtools](https://react.dev/learn/react-developer-tools). + +## Ejemplos + +### Obtención de datos secuencial + +La obtención de datos secuencial ocurre cuando los componentes anidados en un árbol obtienen sus propios datos y las solicitudes no se [eliminan duplicados](/docs/app/deep-dive/caching#request-memoization), lo que lleva a tiempos de respuesta más largos. + +Obtención de datos secuencial y paralela + +Puede haber casos en los que desee este patrón porque una obtención depende del resultado de la otra. + +Por ejemplo, el componente `` solo comenzará a obtener datos una vez que el componente `` haya terminado de obtener datos porque `` depende del prop `artistID`: + +```tsx filename="app/artist/[username]/page.tsx" switcher +export default async function Page({ + params, +}: { + params: Promise<{ username: string }> +}) { + const { username } = await params + // Obtener información del artista + const artist = await getArtist(username) + + return ( + <> +

{artist.name}

+ {/* Mostrar UI de respaldo mientras se carga el componente Playlists */} + Loading...}> + {/* Pasar el ID del artista al componente Playlists */} + + + + ) +} + +async function Playlists({ artistID }: { artistID: string }) { + // Usar el ID del artista para obtener listas de reproducción + const playlists = await getArtistPlaylists(artistID) + + return ( +
    + {playlists.map((playlist) => ( +
  • {playlist.name}
  • + ))} +
+ ) +} +``` + +```jsx filename="app/artist/[username]/page.js" switcher +export default async function Page({ params }) { + const { username } = await params + // Obtener información del artista + const artist = await getArtist(username) + + return ( + <> +

{artist.name}

+ {/* Mostrar UI de respaldo mientras se carga el componente Playlists */} + Loading...}> + {/* Pasar el ID del artista al componente Playlists */} + + + + ) +} + +async function Playlists({ artistID }) { + // Usar el ID del artista para obtener listas de reproducción + const playlists = await getArtistPlaylists(artistID) + + return ( +
    + {playlists.map((playlist) => ( +
  • {playlist.name}
  • + ))} +
+ ) +} +``` + +Para mejorar la experiencia del usuario, debe usar [React ``](/docs/app/building-your-application/routing/loading-ui-and-streaming#streaming-with-suspense) para mostrar un `fallback` mientras se obtienen los datos. Esto permitirá la [transmisión en flujo](#streaming) y evitará que toda la ruta se bloquee por las solicitudes de datos secuenciales. + +### Obtención de datos en paralelo + +La obtención de datos en paralelo ocurre cuando las solicitudes de datos en una ruta se inician de forma anticipada y comienzan al mismo tiempo. + +Por defecto, [los layouts y páginas](/docs/app/getting-started/layouts-and-pages) se renderizan en paralelo. Por lo tanto, cada segmento comienza a obtener datos lo antes posible. + +Sin embargo, dentro de _cualquier_ componente, múltiples solicitudes `async`/`await` aún pueden ser secuenciales si se colocan una después de la otra. Por ejemplo, `getAlbums` se bloqueará hasta que `getArtist` se resuelva: + +```tsx filename="app/artist/[username]/page.tsx" switcher +import { getArtist, getAlbums } from '@/app/lib/data' + +export default async function Page({ params }) { + // Estas solicitudes serán secuenciales + const { username } = await params + const artist = await getArtist(username) + const albums = await getAlbums(username) + return
{artist.name}
+} +``` + +Puedes iniciar solicitudes en paralelo definiéndolas fuera de los componentes que usan los datos y resolviéndolas juntas, por ejemplo, con [`Promise.all`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all): + +```tsx filename="app/artist/[username]/page.tsx" highlight={3,8,23} switcher +import Albums from './albums' + +async function getArtist(username: string) { + const res = await fetch(`https://api.example.com/artist/${username}`) + return res.json() +} + +async function getAlbums(username: string) { + const res = await fetch(`https://api.example.com/artist/${username}/albums`) + return res.json() +} + +export default async function Page({ + params, +}: { + params: Promise<{ username: string }> +}) { + const { username } = await params + const artistData = getArtist(username) + const albumsData = getAlbums(username) + + // Inicia ambas solicitudes en paralelo + const [artist, albums] = await Promise.all([artistData, albumsData]) + + return ( + <> +

{artist.name}

+ + + ) +} +``` + +```jsx filename="app/artist/[username]/page.js" highlight={3,8,19} switcher +import Albums from './albums' + +async function getArtist(username) { + const res = await fetch(`https://api.example.com/artist/${username}`) + return res.json() +} + +async function getAlbums(username) { + const res = await fetch(`https://api.example.com/artist/${username}/albums`) + return res.json() +} + +export default async function Page({ params }) { + const { username } = await params + const artistData = getArtist(username) + const albumsData = getAlbums(username) + + // Inicia ambas solicitudes en paralelo + const [artist, albums] = await Promise.all([artistData, albumsData]) + + return ( + <> +

{artist.name}

+ + + ) +} +``` + +> **Nota importante:** Si una solicitud falla al usar `Promise.all`, toda la operación fallará. Para manejar esto, puedes usar el método [`Promise.allSettled`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled) en su lugar. + +### Precarga de datos + +Puedes precargar datos creando una función de utilidad que llames de forma anticipada antes de las solicitudes bloqueantes. `` se renderiza condicionalmente basado en la función `checkIsAvailable()`. + +Puedes llamar a `preload()` antes de `checkIsAvailable()` para iniciar de forma anticipada las dependencias de datos de ``. Para cuando `` se renderice, sus datos ya habrán sido obtenidos. + +```tsx filename="app/item/[id]/page.tsx" switcher +import { getItem } from '@/lib/data' + +export default async function Page({ + params, +}: { + params: Promise<{ id: string }> +}) { + const { id } = await params + // comienza a cargar los datos del ítem + preload(id) + // realiza otra tarea asíncrona + const isAvailable = await checkIsAvailable() + + return isAvailable ? : null +} + +export const preload = (id: string) => { + // void evalúa la expresión dada y retorna undefined + // https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/void + void getItem(id) +} +export async function Item({ id }: { id: string }) { + const result = await getItem(id) + // ... +} +``` + +```jsx filename="app/item/[id]/page.js" switcher +import { getItem } from '@/lib/data' + +export default async function Page({ params }) { + const { id } = await params + // comienza a cargar los datos del ítem + preload(id) + // realiza otra tarea asíncrona + const isAvailable = await checkIsAvailable() + + return isAvailable ? : null +} + +export const preload = (id) => { + // void evalúa la expresión dada y retorna undefined + // https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/void + void getItem(id) +} +export async function Item({ id }) { + const result = await getItem(id) + // ... +``` + +Además, puedes usar la función [`cache` de React](https://react.dev/reference/react/cache) y el paquete [`server-only`](https://www.npmjs.com/package/server-only) para crear una función de utilidad reutilizable. Este enfoque te permite almacenar en caché la función de obtención de datos y asegurar que solo se ejecute en el servidor. + +```ts filename="utils/get-item.ts" switcher +import { cache } from 'react' +import 'server-only' +import { getItem } from '@/lib/data' + +export const preload = (id: string) => { + void getItem(id) +} + +export const getItem = cache(async (id: string) => { + // ... +}) +``` + +```js filename="utils/get-item.js" switcher +import { cache } from 'react' +import 'server-only' +import { getItem } from '@/lib/data' + +export const preload = (id) => { + void getItem(id) +} + +export const getItem = cache(async (id) => { + // ... +}) +``` diff --git a/apps/docs/content/es/docs/01-app/01-getting-started/09-caching-and-revalidating.mdx b/apps/docs/content/es/docs/01-app/01-getting-started/09-caching-and-revalidating.mdx new file mode 100644 index 00000000..293b9f69 --- /dev/null +++ b/apps/docs/content/es/docs/01-app/01-getting-started/09-caching-and-revalidating.mdx @@ -0,0 +1,252 @@ +--- +source-updated-at: 2025-06-01T01:32:20.000Z +translation-updated-at: 2025-06-02T20:02:43.857Z +title: Cómo almacenar en caché y revalidar datos +nav_title: Almacenamiento en caché y revalidación +description: Aprenda a almacenar en caché y revalidar datos en su aplicación. +related: + title: Referencia de API + description: Obtenga más información sobre las características mencionadas en esta página leyendo la Referencia de API. + links: + - app/api-reference/functions/fetch + - app/api-reference/functions/unstable_cache + - app/api-reference/functions/revalidatePath + - app/api-reference/functions/revalidateTag +--- + +El almacenamiento en caché (caching) es una técnica para guardar el resultado de la obtención de datos y otros cálculos, de modo que las solicitudes futuras para los mismos datos puedan servirse más rápido, sin tener que realizar el trabajo nuevamente. Mientras que la revalidación (revalidation) le permite actualizar las entradas de la caché sin tener que reconstruir toda su aplicación. + +Next.js proporciona algunas APIs para manejar el almacenamiento en caché y la revalidación. Esta guía le mostrará cuándo y cómo usarlas. + +- [`fetch`](#fetch) +- [`unstable_cache`](#unstable_cache) +- [`revalidatePath`](#revalidatepath) +- [`revalidateTag`](#revalidatetag) + +## `fetch` + +Por defecto, las solicitudes [`fetch`](/docs/app/api-reference/functions/fetch) no se almacenan en caché. Puede almacenar en caché solicitudes individuales configurando la opción `cache` en `'force-cache'`. + +```tsx filename="app/page.tsx" switcher +export default async function Page() { + const data = await fetch('https://...', { cache: 'force-cache' }) +} +``` + +```jsx filename="app/page.jsx" switcher +export default async function Page() { + const data = await fetch('https://...', { cache: 'force-cache' }) +} +``` + +> **Nota importante**: Aunque las solicitudes `fetch` no se almacenan en caché por defecto, Next.js [prerrenderizará](/docs/app/getting-started/partial-prerendering#static-rendering) las rutas que tienen solicitudes `fetch` y almacenará en caché el HTML. Si desea garantizar que una ruta sea [dinámica](/docs/app/getting-started/partial-prerendering#dynamic-rendering), use la [API `connection`](/docs/app/api-reference/functions/connection). + +Para revalidar los datos devueltos por una solicitud `fetch`, puede usar la opción `next.revalidate`. + +```tsx filename="app/page.tsx" switcher +export default async function Page() { + const data = await fetch('https://...', { next: { revalidate: 3600 } }) +} +``` + +```jsx filename="app/page.jsx" switcher +export default async function Page() { + const data = await fetch('https://...', { next: { revalidate: 3600 } }) +} +``` + +Esto revalidará los datos después de una cantidad específica de segundos. + +Consulte la [referencia de la API `fetch`](/docs/app/api-reference/functions/fetch) para obtener más información. + +## `unstable_cache` + +`unstable_cache` le permite almacenar en caché el resultado de consultas a bases de datos y otras funciones asíncronas. Para usarlo, envuelva `unstable_cache` alrededor de la función. Por ejemplo: + +```tsx filename="app/lib/data.ts swichter +import { db } from '@/lib/db' +export async function getUserById(id: string) { + return db + .select() + .from(users) + .where(eq(users.id, id)) + .then((res) => res[0]) +} +``` + +```jsx filename="app/lib/data.js" switcher +import { db } from '@/lib/db' + +export async function getUserById(id) { + return db + .select() + .from(users) + .where(eq(users.id, id)) + .then((res) => res[0]) +} +``` + +```tsx filename="app/page.tsx" highlight={2,11,13} switcher +import { unstable_cache } from 'next/cache' +import { getUserById } from '@/app/lib/data' + +export default async function Page({ + params, +}: { + params: Promise<{ userId: string }> +}) { + const { userId } = await params + + const getCachedUser = unstable_cache( + async () => { + return getUserById(userId) + }, + [userId] // agregar el ID de usuario a la clave de caché + ) +} +``` + +```jsx filename="app/page.jsx" highlight={2,7,9} switcher +import { unstable_cache } from 'next/cache'; +import { getUserById } from '@/app/lib/data'; + +export default async function Page({ params } }) { + const { userId } = await params + + const getCachedUser = unstable_cache( + async () => { + return getUserById(userId) + }, + [userId] // agregar el ID de usuario a la clave de caché + ); +} +``` + +La función acepta un tercer objeto opcional para definir cómo se debe revalidar la caché. Acepta: + +- `tags`: un array de etiquetas que Next.js usa para revalidar la caché. +- `revalidate`: el número de segundos después de los cuales se debe revalidar la caché. + +```tsx filename="app/page.tsx" highlight={6-9} switcher +const getCachedUser = unstable_cache( + async () => { + return getUserById(userId) + }, + [userId], + { + tags: ['user'], + revalidate: 3600, + } +) +``` + +```jsx filename="app/page.js" highlight={6-9} switcher +const getCachedUser = unstable_cache( + async () => { + return getUserById(userId) + }, + [userId], + { + tags: ['user'], + revalidate: 3600, + } +) +``` + +Consulte la [referencia de la API `unstable_cache`](/docs/app/api-reference/functions/unstable_cache) para obtener más información. + +## `revalidateTag` + +`revalidateTag` se usa para revalidar entradas de caché basadas en una etiqueta y después de un evento. Para usarlo con `fetch`, comience etiquetando la función con la opción `next.tags`: + +```tsx filename="app/lib/data.ts" highlight={3-5} switcher +export async function getUserById(id: string) { + const data = await fetch(`https://...`, { + next: { + tags: ['user'], + }, + }) +} +``` + +```jsx filename="app/lib/data.js" highlight={3-5} switcher +export async function getUserById(id) { + const data = await fetch(`https://...`, { + next: { + tags: ['user'], + }, + }) +} +``` + +Alternativamente, puede marcar una función `unstable_cache` con la opción `tags`: + +```tsx filename="app/lib/data.ts" highlight={6-8} switcher +export const getUserById = unstable_cache( + async (id: string) => { + return db.query.users.findFirst({ where: eq(users.id, id) }) + }, + ['user'], // Necesario si las variables no se pasan como parámetros + { + tags: ['user'], + } +) +``` + +```jsx filename="app/lib/data.js" highlight={6-8} switcher +export const getUserById = unstable_cache( + async (id) => { + return db.query.users.findFirst({ where: eq(users.id, id) }) + }, + ['user'], // Necesario si las variables no se pasan como parámetros + { + tags: ['user'], + } +) +``` + +Luego, llame a `revalidateTag` en un [Route Handler](/docs/app/api-reference/file-conventions/route) o Server Action: + +```tsx filename="app/lib/actions.ts" highlight={1} switcher +import { revalidateTag } from 'next/cache' + +export async function updateUser(id: string) { + // Mutar datos + revalidateTag('user') +} +``` + +```jsx filename="app/lib/actions.js" highlight={1} switcher +import { revalidateTag } from 'next/cache' + +export async function updateUser(id) { + // Mutar datos + revalidateTag('user') +} +``` + +Puede reutilizar la misma etiqueta en múltiples funciones para revalidarlas todas a la vez. + +Consulte la [referencia de la API `revalidateTag`](/docs/app/api-reference/functions/revalidateTag) para obtener más información. + +## `revalidatePath` + +`revalidatePath` se usa para revalidar una ruta después de un evento. Para usarlo, llámelo en un [Route Handler](/docs/app/api-reference/file-conventions/route) o Server Action: + +```tsx filename="app/lib/actions.ts" highlight={1} switcher +import { revalidatePath } from 'next/cache' + +export async function updateUser(id: string) { + // Mutar datos + revalidatePath('/profile') +``` + +```jsx filename="app/lib/actions.js" highlight={1} switcher +import { revalidatePath } from 'next/cache' + +export async function updateUser(id) { + // Mutar datos + revalidatePath('/profile') +``` + +Consulte la [referencia de la API `revalidatePath`](/docs/app/api-reference/functions/revalidatePath) para obtener más información. \ No newline at end of file diff --git a/apps/docs/content/es/docs/01-app/01-getting-started/10-updating-data.mdx b/apps/docs/content/es/docs/01-app/01-getting-started/10-updating-data.mdx new file mode 100644 index 00000000..e6e2b6e2 --- /dev/null +++ b/apps/docs/content/es/docs/01-app/01-getting-started/10-updating-data.mdx @@ -0,0 +1,352 @@ +--- +source-updated-at: 2025-06-01T01:32:20.000Z +translation-updated-at: 2025-06-02T20:02:54.513Z +title: Cómo actualizar datos +nav_title: Actualización de datos +description: Aprende cómo actualizar datos en tu aplicación Next.js. +related: + title: Referencia de API + description: Obtén más información sobre las características mencionadas en esta página leyendo la Referencia de API. + links: + - app/api-reference/functions/revalidatePath + - app/api-reference/functions/revalidateTag + - app/api-reference/functions/redirect +--- + +Puedes actualizar datos en Next.js utilizando las [Funciones del Servidor (Server Functions)](https://react.dev/reference/rsc/server-functions) de React. Esta página explicará cómo puedes [crear](#creando-funciones-del-servidor) e [invocar](#invocando-funciones-del-servidor) Funciones del Servidor. + +## Funciones del Servidor + +Una Función del Servidor es una función asíncrona que se ejecuta en el servidor. Las Funciones del Servidor son inherentemente asíncronas porque son invocadas por el cliente mediante una solicitud de red. Cuando se invocan como parte de una `action`, también se les llama **Acciones del Servidor (Server Actions)**. + +Por convención, una `action` es una función asíncrona pasada a `startTransition`. Las Funciones del Servidor se envuelven automáticamente con `startTransition` cuando: + +- Se pasan a un `
` usando la prop `action`, +- Se pasan a un ` +} +``` + +```jsx filename="app/ui/button.js" switcher +'use client' + +import { createPost } from '@/app/actions' + +export function Button() { + return +} +``` + +## Invocando Funciones del Servidor + +Hay dos formas principales de invocar una Función del Servidor: + +1. [Formularios](#formularios) en Componentes del Servidor y del Cliente +2. [Manejadores de Eventos](#manejadores-de-eventos) en Componentes del Cliente + +### Formularios + +React extiende el elemento HTML [``](https://react.dev/reference/react-dom/components/form) para permitir que una Función del Servidor sea invocada con la prop `action` de HTML. + +Cuando se invoca en un formulario, la función recibe automáticamente el objeto [`FormData`](https://developer.mozilla.org/docs/Web/API/FormData/FormData). Puedes extraer los datos usando los [métodos nativos de `FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData#instance_methods): + +```tsx filename="app/ui/form.tsx" switcher +import { createPost } from '@/app/actions' + +export function Form() { + return ( + + + + +
+ ) +} +``` + +```jsx filename="app/ui/form.js" switcher +import { createPost } from '@/app/actions' + +export function Form() { + return ( +
+ + + +
+ ) +} +``` + +```ts filename="app/actions.ts" switcher +'use server' + +export async function createPost(formData: FormData) { + const title = formData.get('title') + const content = formData.get('content') + + // Actualizar datos + // Revalidar caché +} +``` + +```js filename="app/actions.js" switcher +'use server' + +export async function createPost(formData) { + const title = formData.get('title') + const content = formData.get('content') + + // Actualizar datos + // Revalidar caché +} +``` + +> **Nota importante:** Cuando se pasan a la prop `action`, las Funciones del Servidor también se conocen como _Acciones del Servidor (Server Actions)_. + +### Manejadores de Eventos + +Puedes invocar una Función del Servidor en un Componente del Cliente usando manejadores de eventos como `onClick`. + +```tsx filename="app/like-button.tsx" switcher +'use client' + +import { incrementLike } from './actions' +import { useState } from 'react' + +export default function LikeButton({ initialLikes }: { initialLikes: number }) { + const [likes, setLikes] = useState(initialLikes) + + return ( + <> +

Me gustas totales: {likes}

+ + + ) +} +``` + +```jsx filename="app/like-button.js" switcher +'use client' + +import { incrementLike } from './actions' +import { useState } from 'react' + +export default function LikeButton({ initialLikes }) { + const [likes, setLikes] = useState(initialLikes) + + return ( + <> +

Me gustas totales: {likes}

+ + + ) +} +``` + +## Ejemplos + +### Mostrando un estado pendiente + +Mientras se ejecuta una Función del Servidor, puedes mostrar un indicador de carga con el hook [`useActionState`](https://react.dev/reference/react/useActionState) de React. Este hook devuelve un booleano `pending`: + +```tsx filename="app/ui/button.tsx" switcher +'use client' + +import { useActionState } from 'react' +import { createPost } from '@/app/actions' +import { LoadingSpinner } from '@/app/ui/loading-spinner' + +export function Button() { + const [state, action, pending] = useActionState(createPost, false) + + return ( + + ) +} +``` + +```jsx filename="app/ui/button.js" switcher +'use client' + +import { useActionState } from 'react' +import { createPost } from '@/app/actions' +import { LoadingSpinner } from '@/app/ui/loading-spinner' + +export function Button() { + const [state, action, pending] = useActionState(createPost, false) + + return ( + + ) +} +``` + +### Revalidando la caché + +Después de realizar una actualización, puedes revalidar la caché de Next.js y mostrar los datos actualizados llamando a [`revalidatePath`](/docs/app/api-reference/functions/revalidatePath) o [`revalidateTag`](/docs/app/api-reference/functions/revalidateTag) dentro de la Función del Servidor: + +```ts filename="app/lib/actions.ts" switcher +import { revalidatePath } from 'next/cache' + +export async function createPost(formData: FormData) { + 'use server' + // Actualizar datos + // ... + + revalidatePath('/posts') +} +``` + +```js filename="app/actions.js" switcher +import { revalidatePath } from 'next/cache' + +export async function createPost(formData) { + 'use server' + // Actualizar datos + // ... + revalidatePath('/posts') +} +``` + +### Redireccionando + +Puedes querer redirigir al usuario a una página diferente después de realizar una actualización. Puedes hacer esto llamando a [`redirect`](/docs/app/api-reference/functions/redirect) dentro de la Función del Servidor: + +```ts filename="app/lib/actions.ts" switcher +'use server' + +import { redirect } from 'next/navigation' + +export async function createPost(formData: FormData) { + // Actualizar datos + // ... + + redirect('/posts') +} +``` + +```js filename="app/actions.js" switcher +'use server' + +import { redirect } from 'next/navigation' + +export async function createPost(formData) { + // Actualizar datos + // ... + + redirect('/posts') +} +``` \ No newline at end of file diff --git a/apps/docs/content/es/docs/01-app/01-getting-started/11-error-handling.mdx b/apps/docs/content/es/docs/01-app/01-getting-started/11-error-handling.mdx new file mode 100644 index 00000000..c7bdf358 --- /dev/null +++ b/apps/docs/content/es/docs/01-app/01-getting-started/11-error-handling.mdx @@ -0,0 +1,317 @@ +--- +source-updated-at: 2025-06-01T01:32:20.000Z +translation-updated-at: 2025-06-02T20:02:45.399Z +title: Cómo manejar errores +nav_title: Manejo de errores +description: Aprenda a mostrar errores esperados y manejar excepciones no capturadas. +related: + title: Referencia de API + description: Conozca más sobre las características mencionadas en esta página leyendo la Referencia de API. + links: + - app/api-reference/functions/redirect + - app/api-reference/file-conventions/error + - app/api-reference/functions/not-found + - app/api-reference/file-conventions/not-found +--- + +Los errores se pueden dividir en dos categorías: [errores esperados](#handling-expected-errors) y [excepciones no capturadas](#handling-uncaught-exceptions). Esta página le guiará sobre cómo puede manejar estos errores en su aplicación Next.js. + +## Manejo de errores esperados + +Los errores esperados son aquellos que pueden ocurrir durante el funcionamiento normal de la aplicación, como los de [validación de formularios del lado del servidor (server-side form validation)](/docs/app/guides/forms) o solicitudes fallidas. Estos errores deben manejarse explícitamente y devolverse al cliente. + +### Funciones del servidor (Server Functions) + +Puede usar el hook [`useActionState`](https://react.dev/reference/react/useActionState) para manejar errores esperados en [Funciones del servidor (Server Functions)](https://react.dev/reference/rsc/server-functions). + +Para estos errores, evite usar bloques `try`/`catch` y lanzar errores. En su lugar, modele los errores esperados como valores de retorno. + +```ts filename="app/actions.ts" switcher +'use server' + +export async function createPost(prevState: any, formData: FormData) { + const title = formData.get('title') + const content = formData.get('content') + + const res = await fetch('https://api.vercel.app/posts', { + method: 'POST', + body: { title, content }, + }) + const json = await res.json() + + if (!res.ok) { + return { message: 'Failed to create post' } + } +} +``` + +```js filename="app/actions.js" switcher +'use server' + +export async function createPost(prevState, formData) { + const title = formData.get('title') + const content = formData.get('content') + + const res = await fetch('https://api.vercel.app/posts', { + method: 'POST', + body: { title, content }, + }) + const json = await res.json() + + if (!res.ok) { + return { message: 'Failed to create post' } + } +} +``` + +Puede pasar su acción al hook `useActionState` y usar el `state` devuelto para mostrar un mensaje de error. + +```tsx filename="app/ui/form.tsx" highlight={11,19} switcher +'use client' + +import { useActionState } from 'react' +import { createPost } from '@/app/actions' + +const initialState = { + message: '', +} + +export function Form() { + const [state, formAction, pending] = useActionState(createPost, initialState) + + return ( +
+ + + +