`:
+
+```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 (
+
+ setIsOpen(true)}>Ver imágenes
+ {/* 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 (
+
+ setIsOpen(true)}>Ver imágenes
+ {/* 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.
+
+
+
+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`.
+
+
+
+```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.
+
+
+
+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 ``.
+
+
+
+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 */}
+
+
+ {/* 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 */}
+
+
+ {/* 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.
+
+
+
+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 `
+ >
+ )
+}
+```
+
+### Streaming
+
+El streaming divide la ruta en fragmentos y los transmite progresivamente al cliente a medida que están listos. Esto permite que el usuario vea partes de la página inmediatamente, antes de que todo el contenido haya terminado de renderizarse.
+
+
+
+En el Renderizado Parcial, los componentes dinámicos envueltos en Suspense comienzan a transmitirse desde el servidor en paralelo.
+
+
+
+Para reducir la sobrecarga de red, la respuesta completa, incluyendo el HTML estático y las partes dinámicas transmitidas, se envía en una **única solicitud HTTP**. Esto evita viajes adicionales y mejora tanto la carga inicial como el rendimiento general.
+
+## Habilitar el Renderizado Parcial
+
+Puedes habilitar PPR añadiendo la opción [`ppr`](https://rc.nextjs.org/docs/app/api-reference/next-config-js/ppr) a tu archivo `next.config.ts`:
+
+```ts filename="next.config.ts" highlight={5} switcher
+import type { NextConfig } from 'next'
+
+const nextConfig: NextConfig = {
+ experimental: {
+ ppr: 'incremental',
+ },
+}
+
+export default nextConfig
+```
+
+```js filename="next.config.js" highlight={4} switcher
+/** @type {import('next').NextConfig} */
+const nextConfig = {
+ experimental: {
+ ppr: 'incremental',
+ },
+}
+```
+
+El valor `'incremental'` te permite adoptar PPR para rutas específicas:
+
+```tsx filename="/app/dashboard/layout.tsx"
+export const experimental_ppr = true
+
+export default function Layout({ children }: { children: React.ReactNode }) {
+ // ...
+}
+```
+
+```jsx filename="/app/dashboard/layout.js"
+export const experimental_ppr = true
+
+export default function Layout({ children }) {
+ // ...
+}
+```
+
+Las rutas que no tengan `experimental_ppr` tendrán como valor predeterminado `false` y no se prerrenderizarán usando PPR. Debes optar explícitamente por PPR para cada ruta.
+
+> **Nota importante**:
+>
+> - `experimental_ppr` se aplicará a todos los hijos del segmento de ruta, incluyendo diseños y páginas anidadas. No es necesario añadirlo a cada archivo, solo al segmento superior de una ruta.
+> - Para deshabilitar PPR en segmentos hijos, puedes establecer `experimental_ppr` en `false` en el segmento hijo.
+
+## Ejemplos
+
+### APIs Dinámicas
+
+Cuando se usan APIs dinámicas que requieren examinar la solicitud entrante, Next.js optará por el renderizado dinámico para la ruta. Para seguir usando PPR, envuelve el componente con Suspense. Por ejemplo, el componente ` ` es dinámico porque usa la API `cookies`:
+
+```jsx filename="app/user.js" switcher
+import { cookies } from 'next/headers'
+
+export async function User() {
+ const session = (await cookies()).get('session')?.value
+ return '...'
+}
+```
+
+```tsx filename="app/user.tsx" switcher
+import { cookies } from 'next/headers'
+
+export async function User() {
+ const session = (await cookies()).get('session')?.value
+ return '...'
+}
+```
+
+El componente ` ` se transmitirá mientras que cualquier otro contenido dentro de ` ` se prerrenderizará y formará parte del shell estático.
+
+```tsx filename="app/page.tsx" switcher
+import { Suspense } from 'react'
+import { User, AvatarSkeleton } from './user'
+
+export const experimental_ppr = true
+
+export default function Page() {
+ return (
+
+ Esto se prerrenderizará
+ }>
+
+
+
+ )
+}
+```
+
+```jsx filename="app/page.js" switcher
+import { Suspense } from 'react'
+import { User, AvatarSkeleton } from './user'
+
+export const experimental_ppr = true
+
+export default function Page() {
+ return (
+
+ Esto se prerrenderizará
+ }>
+
+
+
+ )
+}
+```
+
+### Pasar props dinámicas
+
+Los componentes solo optan por el renderizado dinámico cuando se accede al valor. Por ejemplo, si estás leyendo `searchParams` desde un componente ` `, puedes reenviar este valor a otro componente como prop:
+
+```tsx filename="app/page.tsx" switcher
+import { Table, TableSkeleton } from './table'
+import { Suspense } from 'react'
+
+export default function Page({
+ searchParams,
+}: {
+ searchParams: Promise<{ sort: string }>
+}) {
+ return (
+
+ Esto se prerrenderizará
+ }>
+
+
+
+ )
+}
+```
+
+```jsx filename="app/page.js" switcher
+import { Table, TableSkeleton } from './table'
+import { Suspense } from 'react'
+
+export default function Page({ searchParams }) {
+ return (
+
+ Esto se prerrenderizará
+ }>
+
+
+
+ )
+}
+```
+
+Dentro del componente de tabla, acceder al valor de `searchParams` hará que el componente sea dinámico mientras que el resto de la página se prerrenderizará.
+
+```tsx filename="app/table.tsx" switcher
+export async function Table({
+ searchParams,
+}: {
+ searchParams: Promise<{ sort: string }>
+}) {
+ const sort = (await searchParams).sort === 'true'
+ return '...'
+}
+```
+
+```jsx filename="app/table.js" switcher
+export async function Table({ searchParams }) {
+ const sort = (await searchParams).sort === 'true'
+ return '...'
+}
+```
\ No newline at end of file
diff --git a/apps/docs/content/es/docs/01-app/01-getting-started/13-metadata-and-og-images.mdx b/apps/docs/content/es/docs/01-app/01-getting-started/13-metadata-and-og-images.mdx
new file mode 100644
index 00000000..542db7a8
--- /dev/null
+++ b/apps/docs/content/es/docs/01-app/01-getting-started/13-metadata-and-og-images.mdx
@@ -0,0 +1,322 @@
+---
+source-updated-at: 2025-06-01T01:32:20.000Z
+translation-updated-at: 2025-06-02T20:02:43.630Z
+title: Cómo agregar metadatos y crear imágenes OG
+nav_title: Metadatos e imágenes OG
+description: Aprende cómo agregar metadatos a tus páginas y crear imágenes OG dinámicas.
+related:
+ title: Referencia de API
+ description: Más información sobre las API de Metadata mencionadas en esta página.
+ links:
+ - app/api-reference/functions/generate-metadata
+ - app/api-reference/functions/generate-viewport
+ - app/api-reference/functions/image-response
+ - app/api-reference/file-conventions/metadata
+ - app/api-reference/file-conventions/metadata/app-icons
+ - app/api-reference/file-conventions/metadata/opengraph-image
+ - app/api-reference/file-conventions/metadata/robots
+ - app/api-reference/file-conventions/metadata/sitemap
+---
+
+Las API de Metadata pueden usarse para definir los metadatos de tu aplicación para mejorar el SEO y la capacidad de compartir en la web, e incluyen:
+
+1. [El objeto estático `metadata`](#static-metadata)
+2. [La función dinámica `generateMetadata`](#generated-metadata)
+3. [Convenciones especiales de archivos](/docs/app/api-reference/file-conventions/metadata) que pueden usarse para agregar [favicons](#favicons) e [imágenes OG](#static-open-graph-images) estáticas o generadas dinámicamente.
+
+Con todas las opciones anteriores, Next.js generará automáticamente las etiquetas `` relevantes para tu página, las cuales pueden inspeccionarse en las herramientas de desarrollo del navegador.
+
+## Campos predeterminados
+
+Hay dos etiquetas `meta` predeterminadas que siempre se agregan, incluso si una ruta no define metadatos:
+
+- La [etiqueta meta charset](https://developer.mozilla.org/docs/Web/HTML/Element/meta#attr-charset) establece la codificación de caracteres del sitio web.
+- La [etiqueta meta viewport](https://developer.mozilla.org/docs/Web/HTML/Viewport_meta_tag) establece el ancho y la escala del viewport para que el sitio web se ajuste a diferentes dispositivos.
+
+```html
+
+
+```
+
+Los demás campos de metadatos pueden definirse con el objeto `Metadata` (para [metadatos estáticos](#static-metadata)) o la función `generateMetadata` (para [metadatos generados](#generated-metadata)).
+
+## Metadatos estáticos
+
+Para definir metadatos estáticos, exporta un [objeto `Metadata`](/docs/app/api-reference/functions/generate-metadata#metadata-object) desde un archivo estático [`layout.js`](/docs/app/api-reference/file-conventions/layout) o [`page.js`](/docs/app/api-reference/file-conventions/page). Por ejemplo, para agregar un título y descripción a la ruta del blog:
+
+```tsx filename="app/blog/layout.tsx" switcher
+import type { Metadata } from 'next'
+
+export const metadata: Metadata = {
+ title: 'Mi Blog',
+ description: '...',
+}
+
+export default function Page() {}
+```
+
+```jsx filename="app/blog/layout.tsx" switcher
+export const metadata = {
+ title: 'Mi Blog',
+ description: '...',
+}
+
+export default function Page() {}
+```
+
+Puedes ver una lista completa de opciones disponibles en la [documentación de `generateMetadata`](/docs/app/api-reference/functions/generate-metadata#metadata-fields).
+
+## Metadatos generados
+
+Puedes usar la función [`generateMetadata`](/docs/app/api-reference/functions/generate-metadata) para obtener (`fetch`) metadatos que dependen de datos. Por ejemplo, para obtener el título y descripción de una publicación de blog específica:
+
+```tsx filename="app/blog/[slug]/page.tsx" switcher
+import type { Metadata, ResolvingMetadata } from 'next'
+
+type Props = {
+ params: Promise<{ slug: string }>
+ searchParams: Promise<{ [key: string]: string | string[] | undefined }>
+}
+
+export async function generateMetadata(
+ { params, searchParams }: Props,
+ parent: ResolvingMetadata
+): Promise {
+ const slug = (await params).slug
+
+ // obtener información de la publicación
+ const post = await fetch(`https://api.vercel.app/blog/${slug}`).then((res) =>
+ res.json()
+ )
+
+ return {
+ title: post.title,
+ description: post.description,
+ }
+}
+
+export default function Page({ params, searchParams }: Props) {}
+```
+
+```jsx filename="app/blog/[slug]/page.js" switcher
+export async function generateMetadata({ params, searchParams }, parent) {
+ const slug = (await params).slug
+
+ // obtener información de la publicación
+ const post = await fetch(`https://api.vercel.app/blog/${slug}`).then((res) =>
+ res.json()
+ )
+
+ return {
+ title: post.title,
+ description: post.description,
+ }
+}
+
+export default function Page({ params, searchParams }) {}
+```
+
+Internamente, Next.js transmitirá los metadatos por separado de la interfaz de usuario y los inyectará en el HTML tan pronto como se resuelvan.
+
+### Memorización de solicitudes de datos
+
+Puede haber casos donde necesites obtener los **mismos** datos para los metadatos y la página en sí. Para evitar solicitudes duplicadas, puedes usar la función [`cache` de React](https://react.dev/reference/react/cache) para memorizar el valor de retorno y obtener los datos solo una vez. Por ejemplo, para obtener la información de la publicación de blog tanto para los metadatos como para la página:
+
+```tsx filename="app/lib/data.ts" highlight={5} switcher
+import { cache } from 'react'
+import { db } from '@/app/lib/db'
+
+// getPost se usará dos veces, pero se ejecutará solo una vez
+export const getPost = cache(async (slug: string) => {
+ const res = await db.query.posts.findFirst({ where: eq(posts.slug, slug) })
+ return res
+})
+```
+
+```jsx filename="app/lib/data.js" highlight={5} switcher
+import { cache } from 'react'
+import { db } from '@/app/lib/db'
+
+// getPost se usará dos veces, pero se ejecutará solo una vez
+export const getPost = cache(async (slug) => {
+ const res = await db.query.posts.findFirst({ where: eq(posts.slug, slug) })
+ return res
+})
+```
+
+```tsx filename="app/blog/[slug]/page.tsx" switcher
+import { getPost } from '@/app/lib/data'
+
+export async function generateMetadata({
+ params,
+}: {
+ params: { slug: string }
+}) {
+ const post = await getPost(params.slug)
+ return {
+ title: post.title,
+ description: post.description,
+ }
+}
+
+export default async function Page({ params }: { params: { slug: string } }) {
+ const post = await getPost(params.slug)
+ return {post.title}
+}
+```
+
+```jsx filename="app/blog/[slug]/page.js" switcher
+import { getPost } from '@/app/lib/data'
+
+export async function generateMetadata({ params }) {
+ const post = await getPost(params.slug)
+ return {
+ title: post.title,
+ description: post.description,
+ }
+}
+
+export default async function Page({ params }) {
+ const post = await getPost(params.slug)
+ return {post.title}
+}
+```
+
+## Metadatos basados en archivos
+
+Los siguientes archivos especiales están disponibles para metadatos:
+
+- [favicon.ico, apple-icon.jpg e icon.jpg](/docs/app/api-reference/file-conventions/metadata/app-icons)
+- [opengraph-image.jpg y twitter-image.jpg](/docs/app/api-reference/file-conventions/metadata/opengraph-image)
+- [robots.txt](/docs/app/api-reference/file-conventions/metadata/robots)
+- [sitemap.xml](/docs/app/api-reference/file-conventions/metadata/sitemap)
+
+Puedes usarlos para metadatos estáticos, o generar estos archivos programáticamente con código.
+
+## Favicons
+
+Los favicons son iconos pequeños que representan tu sitio en marcadores y resultados de búsqueda. Para agregar un favicon a tu aplicación, crea un archivo `favicon.ico` y colócalo en la raíz de la carpeta app.
+
+
+
+> También puedes generar favicons programáticamente usando código. Consulta la [documentación de favicons](/docs/app/api-reference/file-conventions/metadata/app-icons) para más información.
+
+## Imágenes Open Graph estáticas
+
+Las imágenes Open Graph (OG) son imágenes que representan tu sitio en redes sociales. Para agregar una imagen OG estática a tu aplicación, crea un archivo `opengraph-image.png` en la raíz de la carpeta app.
+
+
+
+También puedes agregar imágenes OG para rutas específicas creando un archivo `opengraph-image.png` más abajo en la estructura de carpetas. Por ejemplo, para crear una imagen OG específica para la ruta `/blog`, agrega un archivo `opengraph-image.jpg` dentro de la carpeta `blog`.
+
+
+
+La imagen más específica tendrá prioridad sobre cualquier imagen OG que esté por encima en la estructura de carpetas.
+
+> También se admiten otros formatos de imagen como `jpeg`, `png` y `webp`. Consulta la [documentación de Open Graph Image](/docs/app/api-reference/file-conventions/metadata/opengraph-image) para más información.
+
+## Imágenes Open Graph generadas
+
+El constructor [`ImageResponse`](/docs/app/api-reference/functions/image-response) te permite generar imágenes dinámicas usando JSX y CSS. Esto es útil para imágenes OG que dependen de datos.
+
+Por ejemplo, para generar una imagen OG única para cada publicación de blog, agrega un archivo `opengraph-image.ts` dentro de la carpeta `blog` e importa el constructor `ImageResponse` desde `next/og`:
+
+```tsx filename="app/blog/[slug]/opengraph-image.ts" switcher
+import { ImageResponse } from 'next/og'
+import { getPost } from '@/app/lib/data'
+
+// Metadatos de la imagen
+export const size = {
+ width: 1200,
+ height: 630,
+}
+
+export const contentType = 'image/png'
+
+// Generación de imagen
+export default async function Image({ params }: { params: { slug: string } }) {
+ const post = await getPost(params.slug)
+
+ return new ImageResponse(
+ (
+ // Elemento JSX de ImageResponse
+
+ {post.title}
+
+ )
+ )
+}
+```
+
+```jsx filename="app/blog/[slug]/opengraph-image.js" switcher
+import { ImageResponse } from 'next/og'
+import { getPost } from '@/app/lib/data'
+
+// Metadatos de la imagen
+export const size = {
+ width: 1200,
+ height: 630,
+}
+
+export const contentType = 'image/png'
+
+// Generación de imagen
+export default async function Image({ params }) {
+ const post = await getPost(params.slug)
+
+ return new ImageResponse(
+ (
+ // Elemento JSX de ImageResponse
+
+ {post.title}
+
+ )
+ )
+}
+```
+
+`ImageResponse` admite propiedades CSS comunes incluyendo flexbox y posicionamiento absoluto, fuentes personalizadas, ajuste de texto, centrado e imágenes anidadas. [Consulta la lista completa de propiedades CSS admitidas](/docs/app/api-reference/functions/image-response).
+
+> **Nota importante**:
+>
+> - Hay ejemplos disponibles en el [Vercel OG Playground](https://og-playground.vercel.app/).
+> - `ImageResponse` usa [`@vercel/og`](https://vercel.com/docs/og-image-generation), [`satori`](https://github.com/vercel/satori) y `resvg` para convertir HTML y CSS a PNG.
+> - Solo se admiten flexbox y un subconjunto de propiedades CSS. Los diseños avanzados (ej. `display: grid`) no funcionarán.
\ No newline at end of file
diff --git a/apps/docs/content/es/docs/01-app/01-getting-started/14-deploying.mdx b/apps/docs/content/es/docs/01-app/01-getting-started/14-deploying.mdx
new file mode 100644
index 00000000..3a848356
--- /dev/null
+++ b/apps/docs/content/es/docs/01-app/01-getting-started/14-deploying.mdx
@@ -0,0 +1,82 @@
+---
+source-updated-at: 2025-06-01T01:32:20.000Z
+translation-updated-at: 2025-06-02T20:01:31.245Z
+title: Cómo implementar tu aplicación Next.js
+nav_title: Implementación
+description: Aprende cómo implementar tu aplicación Next.js.
+---
+
+Next.js puede implementarse como un servidor Node.js, contenedor Docker, exportación estática o adaptarse para ejecutarse en diferentes plataformas.
+
+| Opción de Implementación | Soporte de Funcionalidades |
+| -------------------------------- | -------------------------- |
+| [Servidor Node.js](#nodejs-server) | Todas |
+| [Contenedor Docker](#docker) | Todas |
+| [Exportación estática](#static-export) | Limitado |
+| [Adaptadores](#adapters) | Depende de la plataforma |
+
+## Servidor Node.js
+
+Next.js puede implementarse en cualquier proveedor que soporte Node.js. Asegúrate de que tu `package.json` tenga los scripts `"build"` y `"start"`:
+
+```json filename="package.json"
+{
+ "scripts": {
+ "dev": "next dev",
+ "build": "next build",
+ "start": "next start"
+ }
+}
+```
+
+Luego, ejecuta `npm run build` para construir tu aplicación y `npm run start` para iniciar el servidor Node.js. Este servidor soporta todas las funcionalidades de Next.js. Si es necesario, también puedes optar por un [servidor personalizado](/docs/app/guides/custom-server).
+
+Las implementaciones con Node.js soportan todas las funcionalidades de Next.js. Aprende cómo [configurarlas](/docs/app/guides/self-hosting) para tu infraestructura.
+
+### Plantillas
+
+- [Flightcontrol](https://github.com/nextjs/deploy-flightcontrol)
+- [Railway](https://github.com/nextjs/deploy-railway)
+- [Replit](https://github.com/nextjs/deploy-replit)
+
+## Docker
+
+Next.js puede implementarse en cualquier proveedor que soporte contenedores [Docker](https://www.docker.com/). Esto incluye orquestadores de contenedores como Kubernetes o proveedores en la nube que ejecuten Docker.
+
+Las implementaciones con Docker soportan todas las funcionalidades de Next.js. Aprende cómo [configurarlas](/docs/app/guides/self-hosting) para tu infraestructura.
+
+### Plantillas
+
+- [Docker](https://github.com/vercel/next.js/tree/canary/examples/with-docker)
+- [Docker Multi-Entorno](https://github.com/vercel/next.js/tree/canary/examples/with-docker-multi-env)
+- [DigitalOcean](https://github.com/nextjs/deploy-digitalocean)
+- [Fly.io](https://github.com/nextjs/deploy-fly)
+- [Google Cloud Run](https://github.com/nextjs/deploy-google-cloud-run)
+- [Render](https://github.com/nextjs/deploy-render)
+- [SST](https://github.com/nextjs/deploy-sst)
+
+## Exportación estática
+
+Next.js permite comenzar como un sitio estático o [Aplicación de Página Única (SPA)](/docs/app/guides/single-page-applications), y luego opcionalmente actualizar para usar funcionalidades que requieran un servidor.
+
+Dado que Next.js soporta [exportaciones estáticas](/docs/app/guides/static-exports), puede implementarse y alojarse en cualquier servidor web que pueda servir activos estáticos HTML/CSS/JS. Esto incluye herramientas como AWS S3, Nginx o Apache.
+
+Ejecutarse como una [exportación estática](/docs/app/guides/static-exports) **no soporta** funcionalidades de Next.js que requieran un servidor. [Aprende más](/docs/app/guides/static-exports#unsupported-features).
+
+### Plantillas
+
+- [GitHub Pages](https://github.com/nextjs/deploy-github-pages)
+
+## Adaptadores
+
+Next.js puede adaptarse para ejecutarse en diferentes plataformas y aprovechar sus capacidades de infraestructura.
+
+Consulta la documentación de cada proveedor para información sobre las funcionalidades soportadas de Next.js:
+
+- [AWS Amplify Hosting](https://docs.amplify.aws/nextjs/start/quickstart/nextjs-app-router-client-components)
+- [Cloudflare](https://developers.cloudflare.com/workers/frameworks/framework-guides/nextjs)
+- [Deno Deploy](https://docs.deno.com/examples/next_tutorial)
+- [Netlify](https://docs.netlify.com/frameworks/next-js/overview/#next-js-support-on-netlify)
+- [Vercel](https://vercel.com/docs/frameworks/nextjs)
+
+> **Nota:** Estamos trabajando en una [API de Adaptadores de Implementación](https://github.com/vercel/next.js/discussions/77740) para que todas las plataformas puedan adoptarla. Una vez completada, añadiremos documentación sobre cómo escribir tus propios adaptadores.
\ No newline at end of file
diff --git a/apps/docs/content/es/docs/01-app/01-getting-started/15-upgrading.mdx b/apps/docs/content/es/docs/01-app/01-getting-started/15-upgrading.mdx
new file mode 100644
index 00000000..f8ad4a94
--- /dev/null
+++ b/apps/docs/content/es/docs/01-app/01-getting-started/15-upgrading.mdx
@@ -0,0 +1,54 @@
+---
+source-updated-at: 2025-06-01T01:32:20.000Z
+translation-updated-at: 2025-06-02T20:00:58.584Z
+title: Cómo actualizar tu aplicación Next.js
+nav_title: Actualización
+description: Aprende cómo actualizar tu aplicación Next.js a la versión más reciente.
+related:
+ title: Guías de versión
+ description: Consulta las guías de versión para obtener instrucciones detalladas de actualización.
+ links:
+ - app/guides/upgrading/version-15
+ - app/guides/upgrading/version-14
+---
+
+## Última versión
+
+Para actualizar a la última versión de Next.js, puedes usar el codemod `upgrade`:
+
+```bash filename="Terminal"
+npx @next/codemod@canary upgrade latest
+```
+
+Si prefieres actualizar manualmente, instala las últimas versiones de Next.js y React:
+
+```bash filename="Terminal"
+npm i next@latest react@latest react-dom@latest eslint-config-next@latest
+```
+
+## Versión canaria
+
+Para actualizar a la última versión canaria, asegúrate de estar en la última versión estable de Next.js y que todo funcione correctamente. Luego, ejecuta el siguiente comando:
+
+```bash filename="Terminal"
+npm i next@canary
+```
+
+### Funcionalidades disponibles en canaria
+
+Las siguientes funcionalidades están actualmente disponibles en la versión canaria:
+
+**Almacenamiento en caché**:
+
+- [`"use cache"`](/docs/app/api-reference/directives/use-cache)
+- [`cacheLife`](/docs/app/api-reference/functions/cacheLife)
+- [`cacheTag`](/docs/app/api-reference/functions/cacheTag)
+- [`dynamicIO`](/docs/app/api-reference/config/next-config-js/dynamicIO)
+
+**Autenticación**:
+
+- [`forbidden`](/docs/app/api-reference/functions/forbidden)
+- [`unauthorized`](/docs/app/api-reference/functions/unauthorized)
+- [`forbidden.js`](/docs/app/api-reference/file-conventions/forbidden)
+- [`unauthorized.js`](/docs/app/api-reference/file-conventions/unauthorized)
+- [`authInterrupts`](/docs/app/api-reference/config/next-config-js/authInterrupts)
\ No newline at end of file
diff --git a/apps/docs/content/es/docs/01-app/01-getting-started/index.mdx b/apps/docs/content/es/docs/01-app/01-getting-started/index.mdx
new file mode 100644
index 00000000..761d20d3
--- /dev/null
+++ b/apps/docs/content/es/docs/01-app/01-getting-started/index.mdx
@@ -0,0 +1,23 @@
+---
+source-updated-at: 2025-05-16T04:52:11.000Z
+translation-updated-at: 2025-06-02T20:00:42.540Z
+title: Primeros pasos
+description: Aprenda a crear aplicaciones web full-stack con el App Router de Next.js.
+---
+
+¡Bienvenido a la documentación de Next.js!
+
+Esta sección de **Primeros pasos** le ayudará a crear su primera aplicación Next.js y aprender las características principales que utilizará en cada proyecto.
+
+## Conocimientos previos
+
+Nuestra documentación asume cierta familiaridad con el desarrollo web. Antes de comenzar, será útil si está cómodo con:
+
+- HTML
+- CSS
+- JavaScript
+- React
+
+Si es nuevo en React o necesita un repaso, le recomendamos comenzar con nuestro [curso Fundamentos de React](/learn/react-foundations) y el [curso Fundamentos de Next.js](/learn/dashboard-app) donde construirá una aplicación mientras aprende.
+
+## Próximos pasos
\ No newline at end of file
diff --git a/apps/docs/content/es/docs/01-app/02-guides/analytics.mdx b/apps/docs/content/es/docs/01-app/02-guides/analytics.mdx
new file mode 100644
index 00000000..d27bc8f7
--- /dev/null
+++ b/apps/docs/content/es/docs/01-app/02-guides/analytics.mdx
@@ -0,0 +1,234 @@
+---
+source-updated-at: 2025-05-19T22:31:51.000Z
+translation-updated-at: 2025-06-02T20:01:48.802Z
+title: Cómo agregar análisis a tu aplicación Next.js
+nav_title: Análisis
+description: Mide y rastrea el rendimiento de la página usando Next.js Speed Insights
+---
+
+{/* El contenido de este documento se comparte entre el enrutador de app y pages. Puedes usar el componente `Contenido ` para agregar contenido específico del enrutador Pages. Cualquier contenido compartido no debe estar envuelto en un componente. */}
+
+Next.js tiene soporte integrado para medir y reportar métricas de rendimiento. Puedes usar el hook [`useReportWebVitals`](/docs/app/api-reference/functions/use-report-web-vitals) para gestionar los reportes manualmente, o alternativamente, Vercel ofrece un [servicio administrado](https://vercel.com/analytics?utm_source=next-site&utm_medium=docs&utm_campaign=next-website) para recolectar y visualizar métricas automáticamente.
+
+## Instrumentación del Cliente
+
+Para necesidades más avanzadas de análisis y monitoreo, Next.js proporciona un archivo `instrumentation-client.js|ts` que se ejecuta antes de que comience el código frontend de tu aplicación. Esto es ideal para configurar herramientas globales de análisis, seguimiento de errores o monitoreo de rendimiento.
+
+Para usarlo, crea un archivo `instrumentation-client.js` o `instrumentation-client.ts` en el directorio raíz de tu aplicación:
+
+```js filename="instrumentation-client.js"
+// Inicializar análisis antes de que la aplicación comience
+console.log('Análisis inicializado')
+
+// Configurar seguimiento global de errores
+window.addEventListener('error', (event) => {
+ // Enviar a tu servicio de seguimiento de errores
+ reportError(event.error)
+})
+```
+
+## Construye tu Propia Solución
+
+
+
+```jsx filename="pages/_app.js"
+import { useReportWebVitals } from 'next/web-vitals'
+
+function MyApp({ Component, pageProps }) {
+ useReportWebVitals((metric) => {
+ console.log(metric)
+ })
+
+ return
+}
+```
+
+Consulta la [Referencia de API](/docs/pages/api-reference/functions/use-report-web-vitals) para más información.
+
+
+
+
+
+```jsx filename="app/_components/web-vitals.js"
+'use client'
+
+import { useReportWebVitals } from 'next/web-vitals'
+
+export function WebVitals() {
+ useReportWebVitals((metric) => {
+ console.log(metric)
+ })
+}
+```
+
+```jsx filename="app/layout.js"
+import { WebVitals } from './_components/web-vitals'
+
+export default function Layout({ children }) {
+ return (
+
+
+
+ {children}
+
+
+ )
+}
+```
+
+> Dado que el hook `useReportWebVitals` requiere la directiva `'use client'`, el enfoque más eficiente es crear un componente separado que el layout raíz importe. Esto limita el límite del cliente exclusivamente al componente `WebVitals`.
+
+Consulta la [Referencia de API](/docs/app/api-reference/functions/use-report-web-vitals) para más información.
+
+
+
+## Web Vitals
+
+[Web Vitals](https://web.dev/vitals/) son un conjunto de métricas útiles que buscan capturar la experiencia del usuario en una página web. Las siguientes métricas de Web Vitals están incluidas:
+
+- [Tiempo hasta el primer byte](https://developer.mozilla.org/docs/Glossary/Time_to_first_byte) (TTFB)
+- [Primera pintura con contenido](https://developer.mozilla.org/docs/Glossary/First_contentful_paint) (FCP)
+- [Pintura con contenido más grande](https://web.dev/lcp/) (LCP)
+- [Retraso de la primera entrada](https://web.dev/fid/) (FID)
+- [Cambio de diseño acumulativo](https://web.dev/cls/) (CLS)
+- [Interacción a la siguiente pintura](https://web.dev/inp/) (INP)
+
+Puedes manejar todos los resultados de estas métricas usando la propiedad `name`.
+
+
+
+```jsx filename="pages/_app.js"
+import { useReportWebVitals } from 'next/web-vitals'
+
+function MyApp({ Component, pageProps }) {
+ useReportWebVitals((metric) => {
+ switch (metric.name) {
+ case 'FCP': {
+ // manejar resultados FCP
+ }
+ case 'LCP': {
+ // manejar resultados LCP
+ }
+ // ...
+ }
+ })
+
+ return
+}
+```
+
+
+
+
+
+```tsx filename="app/_components/web-vitals.tsx" switcher
+'use client'
+
+import { useReportWebVitals } from 'next/web-vitals'
+
+export function WebVitals() {
+ useReportWebVitals((metric) => {
+ switch (metric.name) {
+ case 'FCP': {
+ // manejar resultados FCP
+ }
+ case 'LCP': {
+ // manejar resultados LCP
+ }
+ // ...
+ }
+ })
+}
+```
+
+```jsx filename="app/_components/web-vitals.js" switcher
+'use client'
+
+import { useReportWebVitals } from 'next/web-vitals'
+
+export function WebVitals() {
+ useReportWebVitals((metric) => {
+ switch (metric.name) {
+ case 'FCP': {
+ // manejar resultados FCP
+ }
+ case 'LCP': {
+ // manejar resultados LCP
+ }
+ // ...
+ }
+ })
+}
+```
+
+
+
+
+
+## Métricas Personalizadas
+
+Además de las métricas principales mencionadas anteriormente, hay algunas métricas personalizadas adicionales que miden el tiempo que tarda la página en hidratarse y renderizarse:
+
+- `Next.js-hydration`: Tiempo que tarda la página en comenzar y finalizar la hidratación (en ms)
+- `Next.js-route-change-to-render`: Tiempo que tarda una página en comenzar a renderizarse después de un cambio de ruta (en ms)
+- `Next.js-render`: Tiempo que tarda una página en finalizar el renderizado después de un cambio de ruta (en ms)
+
+Puedes manejar todos los resultados de estas métricas por separado:
+
+```js
+export function reportWebVitals(metric) {
+ switch (metric.name) {
+ case 'Next.js-hydration':
+ // manejar resultados de hidratación
+ break
+ case 'Next.js-route-change-to-render':
+ // manejar resultados de cambio de ruta a renderizado
+ break
+ case 'Next.js-render':
+ // manejar resultados de renderizado
+ break
+ default:
+ break
+ }
+}
+```
+
+Estas métricas funcionan en todos los navegadores que admiten la [API de User Timing](https://caniuse.com/#feat=user-timing).
+
+
+
+## Enviando resultados a sistemas externos
+
+Puedes enviar resultados a cualquier endpoint para medir y rastrear el rendimiento real de los usuarios en tu sitio. Por ejemplo:
+
+```js
+useReportWebVitals((metric) => {
+ const body = JSON.stringify(metric)
+ const url = 'https://example.com/analytics'
+
+ // Usar `navigator.sendBeacon()` si está disponible, recurriendo a `fetch()`.
+ if (navigator.sendBeacon) {
+ navigator.sendBeacon(url, body)
+ } else {
+ fetch(url, { body, method: 'POST', keepalive: true })
+ }
+})
+```
+
+> **Nota importante**: Si usas [Google Analytics](https://analytics.google.com/analytics/web/), el valor `id` te permite construir distribuciones de métricas manualmente (para calcular percentiles, etc.)
+
+> ```js
+> useReportWebVitals((metric) => {
+> // Usar `window.gtag` si inicializaste Google Analytics como en este ejemplo:
+> // https://github.com/vercel/next.js/blob/canary/examples/with-google-analytics
+> window.gtag('event', metric.name, {
+> value: Math.round(
+> metric.name === 'CLS' ? metric.value * 1000 : metric.value
+> ), // los valores deben ser enteros
+> event_label: metric.id, // id único para la carga actual de la página
+> non_interaction: true, // evita afectar la tasa de rebote.
+> })
+> })
+> ```
+>
+> Lee más sobre [enviar resultados a Google Analytics](https://github.com/GoogleChrome/web-vitals#send-the-results-to-google-analytics).
\ No newline at end of file
diff --git a/apps/docs/content/es/docs/01-app/02-guides/authentication.mdx b/apps/docs/content/es/docs/01-app/02-guides/authentication.mdx
new file mode 100644
index 00000000..a6b7bc64
--- /dev/null
+++ b/apps/docs/content/es/docs/01-app/02-guides/authentication.mdx
@@ -0,0 +1,1653 @@
+---
+source-updated-at: 2025-06-01T01:32:20.000Z
+translation-updated-at: 2025-06-02T20:10:21.393Z
+title: Cómo implementar autenticación en Next.js
+nav_title: Autenticación
+description: Aprende cómo implementar autenticación en tu aplicación Next.js.
+---
+
+Comprender la autenticación es crucial para proteger los datos de tu aplicación. Esta página te guiará a través de las características de React y Next.js que puedes utilizar para implementar autenticación.
+
+Antes de comenzar, es útil dividir el proceso en tres conceptos:
+
+1. **[Autenticación](#authentication)**: Verifica si el usuario es quien dice ser. Requiere que el usuario demuestre su identidad con algo que posee, como un nombre de usuario y contraseña.
+2. **[Gestión de sesiones](#session-management)**: Rastrea el estado de autenticación del usuario entre solicitudes.
+3. **[Autorización](#authorization)**: Decide qué rutas y datos puede acceder el usuario.
+
+Este diagrama muestra el flujo de autenticación utilizando características de React y Next.js:
+
+
+
+Los ejemplos en esta página explican una autenticación básica con nombre de usuario y contraseña con fines educativos. Aunque puedes implementar una solución de autenticación personalizada, para mayor seguridad y simplicidad, recomendamos usar una biblioteca de autenticación. Estas ofrecen soluciones incorporadas para autenticación, gestión de sesiones y autorización, así como características adicionales como inicios de sesión sociales, autenticación multifactor y control de acceso basado en roles. Puedes encontrar una lista en la sección [Bibliotecas de Autenticación](#auth-libraries).
+
+## Autenticación
+
+
+
+### Funcionalidad de registro e inicio de sesión
+
+Puedes usar el elemento [``](https://react.dev/reference/react-dom/components/form) con [Server Actions](/docs/app/building-your-application/data-fetching/server-actions-and-mutations) de React y `useActionState` para capturar credenciales de usuario, validar campos del formulario y llamar a la API o base de datos de tu Proveedor de Autenticación.
+
+Dado que las Server Actions siempre se ejecutan en el servidor, proporcionan un entorno seguro para manejar la lógica de autenticación.
+
+Aquí están los pasos para implementar la funcionalidad de registro/inicio de sesión:
+
+#### 1. Capturar credenciales del usuario
+
+Para capturar credenciales del usuario, crea un formulario que invoque una Server Action al enviarse. Por ejemplo, un formulario de registro que acepte el nombre, correo electrónico y contraseña del usuario:
+
+```tsx filename="app/ui/signup-form.tsx" switcher
+import { signup } from '@/app/actions/auth'
+
+export function SignupForm() {
+ return (
+
+
+ Nombre
+
+
+
+ Correo electrónico
+
+
+
+ Contraseña
+
+
+ Registrarse
+
+ )
+}
+```
+
+```jsx filename="app/ui/signup-form.js" switcher
+import { signup } from '@/app/actions/auth'
+
+export function SignupForm() {
+ return (
+
+
+ Nombre
+
+
+
+ Correo electrónico
+
+
+
+ Contraseña
+
+
+ Registrarse
+
+ )
+}
+```
+
+```tsx filename="app/actions/auth.ts" switcher
+export async function signup(formData: FormData) {}
+```
+
+```jsx filename="app/actions/auth.js" switcher
+export async function signup(formData) {}
+```
+
+#### 2. Validar campos del formulario en el servidor
+
+Usa la Server Action para validar los campos del formulario en el servidor. Si tu proveedor de autenticación no proporciona validación de formularios, puedes usar una biblioteca de validación de esquemas como [Zod](https://zod.dev/) o [Yup](https://github.com/jquense/yup).
+
+Usando Zod como ejemplo, puedes definir un esquema de formulario con mensajes de error apropiados:
+
+```ts filename="app/lib/definitions.ts" switcher
+import { z } from 'zod'
+
+export const SignupFormSchema = z.object({
+ name: z
+ .string()
+ .min(2, { message: 'El nombre debe tener al menos 2 caracteres.' })
+ .trim(),
+ email: z.string().email({ message: 'Por favor ingresa un correo electrónico válido.' }).trim(),
+ password: z
+ .string()
+ .min(8, { message: 'Debe tener al menos 8 caracteres' })
+ .regex(/[a-zA-Z]/, { message: 'Debe contener al menos una letra.' })
+ .regex(/[0-9]/, { message: 'Debe contener al menos un número.' })
+ .regex(/[^a-zA-Z0-9]/, {
+ message: 'Debe contener al menos un carácter especial.',
+ })
+ .trim(),
+})
+
+export type FormState =
+ | {
+ errors?: {
+ name?: string[]
+ email?: string[]
+ password?: string[]
+ }
+ message?: string
+ }
+ | undefined
+```
+
+```js filename="app/lib/definitions.js" switcher
+import { z } from 'zod'
+
+export const SignupFormSchema = z.object({
+ name: z
+ .string()
+ .min(2, { message: 'El nombre debe tener al menos 2 caracteres.' })
+ .trim(),
+ email: z.string().email({ message: 'Por favor ingresa un correo electrónico válido.' }).trim(),
+ password: z
+ .string()
+ .min(8, { message: 'Debe tener al menos 8 caracteres' })
+ .regex(/[a-zA-Z]/, { message: 'Debe contener al menos una letra.' })
+ .regex(/[0-9]/, { message: 'Debe contener al menos un número.' })
+ .regex(/[^a-zA-Z0-9]/, {
+ message: 'Debe contener al menos un carácter especial.',
+ })
+ .trim(),
+})
+```
+
+Para evitar llamadas innecesarias a la API o base de datos de tu proveedor de autenticación, puedes hacer un `return` anticipado en la Server Action si algún campo del formulario no coincide con el esquema definido.
+
+```ts filename="app/actions/auth.ts" switcher
+import { SignupFormSchema, FormState } from '@/app/lib/definitions'
+
+export async function signup(state: FormState, formData: FormData) {
+ // Validar campos del formulario
+ const validatedFields = SignupFormSchema.safeParse({
+ name: formData.get('name'),
+ email: formData.get('email'),
+ password: formData.get('password'),
+ })
+
+ // Si algún campo del formulario es inválido, retornar anticipadamente
+ if (!validatedFields.success) {
+ return {
+ errors: validatedFields.error.flatten().fieldErrors,
+ }
+ }
+
+ // Llamar al proveedor o base de datos para crear un usuario...
+}
+```
+
+```js filename="app/actions/auth.js" switcher
+import { SignupFormSchema } from '@/app/lib/definitions'
+
+export async function signup(state, formData) {
+ // Validar campos del formulario
+ const validatedFields = SignupFormSchema.safeParse({
+ name: formData.get('name'),
+ email: formData.get('email'),
+ password: formData.get('password'),
+ })
+
+ // Si algún campo del formulario es inválido, retornar anticipadamente
+ if (!validatedFields.success) {
+ return {
+ errors: validatedFields.error.flatten().fieldErrors,
+ }
+ }
+
+ // Llamar al proveedor o base de datos para crear un usuario...
+}
+```
+
+De vuelta en tu ` `, puedes usar el hook `useActionState` de React para mostrar errores de validación mientras se envía el formulario:
+
+```tsx filename="app/ui/signup-form.tsx" switcher highlight={7,15,21,27-36}
+'use client'
+
+import { signup } from '@/app/actions/auth'
+import { useActionState } from 'react'
+
+export default function SignupForm() {
+ const [state, action, pending] = useActionState(signup, undefined)
+
+ return (
+
+
+ Nombre
+
+
+ {state?.errors?.name && {state.errors.name}
}
+
+
+ Correo electrónico
+
+
+ {state?.errors?.email && {state.errors.email}
}
+
+
+ Contraseña
+
+
+ {state?.errors?.password && (
+
+
La contraseña debe:
+
+ {state.errors.password.map((error) => (
+ - {error}
+ ))}
+
+
+ )}
+
+ Registrarse
+
+
+ )
+}
+```
+
+```jsx filename="app/ui/signup-form.js" switcher highlight={7,15,21,27-36}
+'use client'
+
+import { signup } from '@/app/actions/auth'
+import { useActionState } from 'react'
+
+export default function SignupForm() {
+ const [state, action, pending] = useActionState(signup, undefined)
+
+ return (
+
+
+ Nombre
+
+
+ {state?.errors?.name && {state.errors.name}
}
+
+
+ Correo electrónico
+
+
+ {state?.errors?.email && {state.errors.email}
}
+
+
+ Contraseña
+
+
+ {state?.errors?.password && (
+
+
La contraseña debe:
+
+ {state.errors.password.map((error) => (
+ - {error}
+ ))}
+
+
+ )}
+
+ Registrarse
+
+
+ )
+}
+```
+
+> **Nota importante:**
+>
+> - En React 19, `useFormStatus` incluye claves adicionales en el objeto devuelto, como data, method y action. Si no estás usando React 19, solo está disponible la clave `pending`.
+> - Antes de mutar datos, siempre debes asegurarte de que un usuario también esté autorizado para realizar la acción. Consulta [Autenticación y Autorización](#authorization).
+
+#### 3. Crear un usuario o verificar credenciales
+
+Después de validar los campos del formulario, puedes crear una nueva cuenta de usuario o verificar si el usuario existe llamando a la API o base de datos de tu proveedor de autenticación.
+
+Continuando con el ejemplo anterior:
+
+```tsx filename="app/actions/auth.tsx" switcher
+export async function signup(state: FormState, formData: FormData) {
+ // 1. Validar campos del formulario
+ // ...
+
+ // 2. Preparar datos para inserción en la base de datos
+ const { name, email, password } = validatedFields.data
+ // Ejemplo: Hashear la contraseña antes de almacenarla
+ const hashedPassword = await bcrypt.hash(password, 10)
+
+ // 3. Insertar el usuario en la base de datos o llamar a la API de una biblioteca de autenticación
+ const data = await db
+ .insert(users)
+ .values({
+ name,
+ email,
+ password: hashedPassword,
+ })
+ .returning({ id: users.id })
+
+ const user = data[0]
+
+ if (!user) {
+ return {
+ message: 'Ocurrió un error al crear tu cuenta.',
+ }
+ }
+
+ // TODO:
+ // 4. Crear sesión de usuario
+ // 5. Redirigir al usuario
+}
+```
+
+```jsx filename="app/actions/auth.js" switcher
+export async function signup(state, formData) {
+ // 1. Validar campos del formulario
+ // ...
+
+ // 2. Preparar datos para inserción en la base de datos
+ const { name, email, password } = validatedFields.data
+ // Ejemplo: Hashear la contraseña antes de almacenarla
+ const hashedPassword = await bcrypt.hash(password, 10)
+
+ // 3. Insertar el usuario en la base de datos o llamar a una API de biblioteca
+ const data = await db
+ .insert(users)
+ .values({
+ name,
+ email,
+ password: hashedPassword,
+ })
+ .returning({ id: users.id })
+
+ const user = data[0]
+
+ if (!user) {
+ return {
+ message: 'Ocurrió un error al crear tu cuenta.',
+ }
+ }
+
+ // TODO:
+ // 4. Crear sesión de usuario
+ // 5. Redirigir al usuario
+}
+```
+
+Después de crear exitosamente la cuenta de usuario o verificar las credenciales, puedes crear una sesión para gestionar el estado de autenticación del usuario. Dependiendo de tu estrategia de gestión de sesiones, esta puede almacenarse en una cookie, base de datos o ambas. Continúa en la sección de [Gestión de Sesiones](#session-management) para aprender más.
+
+> **Consejos:**
+>
+> - El ejemplo anterior es detallado ya que desglosa los pasos de autenticación con fines educativos. Esto resalta que implementar tu propia solución segura puede volverse complejo rápidamente. Considera usar una [Biblioteca de Autenticación](#auth-libraries) para simplificar el proceso.
+> - Para mejorar la experiencia del usuario, podrías verificar correos electrónicos o nombres de usuario duplicados antes en el flujo de registro. Por ejemplo, mientras el usuario escribe un nombre de usuario o cuando el campo de entrada pierde el foco. Esto puede evitar envíos innecesarios del formulario y proporcionar retroalimentación inmediata al usuario. Puedes controlar la frecuencia de estas verificaciones con bibliotecas como [use-debounce](https://www.npmjs.com/package/use-debounce).
+
+
+
+
+
+Estos son los pasos para implementar un formulario de registro y/o inicio de sesión:
+
+1. El usuario envía sus credenciales a través de un formulario.
+2. El formulario envía una solicitud manejada por una ruta de API.
+3. Tras una verificación exitosa, el proceso se completa, indicando la autenticación exitosa del usuario.
+4. Si la verificación falla, se muestra un mensaje de error.
+
+Considera un formulario de inicio de sesión donde los usuarios pueden ingresar sus credenciales:
+
+```tsx filename="pages/login.tsx" switcher
+import { FormEvent } from 'react'
+import { useRouter } from 'next/router'
+
+export default function LoginPage() {
+ const router = useRouter()
+
+ async function handleSubmit(event: FormEvent) {
+ event.preventDefault()
+
+ const formData = new FormData(event.currentTarget)
+ const email = formData.get('email')
+ const password = formData.get('password')
+
+ const response = await fetch('/api/auth/login', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ email, password }),
+ })
+
+ if (response.ok) {
+ router.push('/profile')
+ } else {
+ // Manejar errores
+ }
+ }
+
+ return (
+
+
+
+ Iniciar sesión
+
+ )
+}
+```
+
+```jsx filename="pages/login.jsx" switcher
+import { FormEvent } from 'react'
+import { useRouter } from 'next/router'
+
+export default function LoginPage() {
+ const router = useRouter()
+
+ async function handleSubmit(event) {
+ event.preventDefault()
+
+ const formData = new FormData(event.currentTarget)
+ const email = formData.get('email')
+ const password = formData.get('password')
+
+ const response = await fetch('/api/auth/login', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ email, password }),
+ })
+
+ if (response.ok) {
+ router.push('/profile')
+ } else {
+ // Manejar errores
+ }
+ }
+
+ return (
+
+
+
+ Iniciar sesión
+
+ )
+}
+```
+
+El formulario anterior tiene dos campos de entrada para capturar el correo electrónico y la contraseña del usuario. Al enviarlo, se activa una función que envía una solicitud POST a una ruta de API (`/api/auth/login`).
+
+Luego puedes llamar a la API de tu Proveedor de Autenticación en la ruta de API para manejar la autenticación:
+
+```ts filename="pages/api/auth/login.ts" switcher
+import type { NextApiRequest, NextApiResponse } from 'next'
+import { signIn } from '@/auth'
+
+export default async function handler(
+ req: NextApiRequest,
+ res: NextApiResponse
+) {
+ try {
+ const { email, password } = req.body
+ await signIn('credentials', { email, password })
+
+ res.status(200).json({ success: true })
+ } catch (error) {
+ if (error.type === 'CredentialsSignin') {
+ res.status(401).json({ error: 'Credenciales inválidas.' })
+ } else {
+ res.status(500).json({ error: 'Algo salió mal.' })
+ }
+ }
+}
+```
+
+```js filename="pages/api/auth/login.js" switcher
+import { signIn } from '@/auth'
+
+export default async function handler(req, res) {
+ try {
+ const { email, password } = req.body
+ await signIn('credentials', { email, password })
+
+ res.status(200).json({ success: true })
+ } catch (error) {
+ if (error.type === 'CredentialsSignin') {
+ res.status(401).json({ error: 'Credenciales inválidas.' })
+ } else {
+ res.status(500).json({ error: 'Algo salió mal.' })
+ }
+ }
+}
+```
+
+
+
+## Gestión de Sesiones
+
+La gestión de sesiones asegura que el estado autenticado del usuario se mantenga entre solicitudes. Incluye crear, almacenar, actualizar y eliminar sesiones o tokens.
+
+Existen dos tipos de sesiones:
+
+1. [**Sin estado (Stateless)**](#stateless-sessions): Los datos de la sesión (o un token) se almacenan en las cookies del navegador. La cookie se envía con cada solicitud, permitiendo verificar la sesión en el servidor. Este método es más simple, pero puede ser menos seguro si no se implementa correctamente.
+2. [**Base de datos**](#database-sessions): Los datos de la sesión se almacenan en una base de datos, y el navegador del usuario solo recibe el ID de sesión encriptado. Este método es más seguro, pero puede ser complejo y consumir más recursos del servidor.
+
+> **Es bueno saber:** Aunque puedes usar cualquiera de los métodos, o ambos, recomendamos usar una biblioteca de gestión de sesiones como [iron-session](https://github.com/vvo/iron-session) o [Jose](https://github.com/panva/jose).
+
+### Sesiones sin estado (Stateless)
+
+
+
+Para crear y gestionar sesiones sin estado, hay algunos pasos que debes seguir:
+
+1. Generar una clave secreta, que se usará para firmar tu sesión, y almacenarla como una [variable de entorno](/docs/app/guides/environment-variables).
+2. Escribir lógica para encriptar/desencriptar datos de sesión usando una biblioteca de gestión de sesiones.
+3. Gestionar cookies usando la API [`cookies`](/docs/app/api-reference/functions/cookies) de Next.js.
+
+Además de lo anterior, considera añadir funcionalidad para [actualizar (o refrescar)](#updating-or-refreshing-sessions) la sesión cuando el usuario regrese a la aplicación, y [eliminar](#deleting-the-session) la sesión cuando el usuario cierre sesión.
+
+> **Es bueno saber:** Verifica si tu [biblioteca de autenticación](#auth-libraries) incluye gestión de sesiones.
+
+#### 1. Generar una clave secreta
+
+Hay varias formas de generar una clave secreta para firmar tu sesión. Por ejemplo, puedes usar el comando `openssl` en tu terminal:
+
+```bash filename="terminal"
+openssl rand -base64 32
+```
+
+Este comando genera una cadena aleatoria de 32 caracteres que puedes usar como tu clave secreta y almacenar en tu [archivo de variables de entorno](/docs/app/guides/environment-variables):
+
+```bash filename=".env"
+SESSION_SECRET=tu_clave_secreta
+```
+
+Luego puedes referenciar esta clave en tu lógica de gestión de sesiones:
+
+```js filename="app/lib/session.js"
+const secretKey = process.env.SESSION_SECRET
+```
+
+#### 2. Encriptar y desencriptar sesiones
+
+A continuación, puedes usar tu [biblioteca de gestión de sesiones](#session-management-libraries) preferida para encriptar y desencriptar sesiones. Continuando con el ejemplo anterior, usaremos [Jose](https://www.npmjs.com/package/jose) (compatible con el [Edge Runtime](/docs/app/api-reference/edge)) y el paquete [`server-only`](https://www.npmjs.com/package/server-only) de React para asegurar que tu lógica de gestión de sesiones solo se ejecute en el servidor.
+
+```tsx filename="app/lib/session.ts" switcher
+import 'server-only'
+import { SignJWT, jwtVerify } from 'jose'
+import { SessionPayload } from '@/app/lib/definitions'
+
+const secretKey = process.env.SESSION_SECRET
+const encodedKey = new TextEncoder().encode(secretKey)
+
+export async function encrypt(payload: SessionPayload) {
+ return new SignJWT(payload)
+ .setProtectedHeader({ alg: 'HS256' })
+ .setIssuedAt()
+ .setExpirationTime('7d')
+ .sign(encodedKey)
+}
+
+export async function decrypt(session: string | undefined = '') {
+ try {
+ const { payload } = await jwtVerify(session, encodedKey, {
+ algorithms: ['HS256'],
+ })
+ return payload
+ } catch (error) {
+ console.log('Error al verificar la sesión')
+ }
+}
+```
+
+```jsx filename="app/lib/session.js" switcher
+import 'server-only'
+import { SignJWT, jwtVerify } from 'jose'
+
+const secretKey = process.env.SESSION_SECRET
+const encodedKey = new TextEncoder().encode(secretKey)
+
+export async function encrypt(payload) {
+ return new SignJWT(payload)
+ .setProtectedHeader({ alg: 'HS256' })
+ .setIssuedAt()
+ .setExpirationTime('7d')
+ .sign(encodedKey)
+}
+
+export async function decrypt(session) {
+ try {
+ const { payload } = await jwtVerify(session, encodedKey, {
+ algorithms: ['HS256'],
+ })
+ return payload
+ } catch (error) {
+ console.log('Error al verificar la sesión')
+ }
+}
+```
+
+> **Consejos**:
+>
+> - El payload debe contener los datos de usuario **mínimos** y únicos que se usarán en solicitudes posteriores, como el ID del usuario, rol, etc. No debe contener información personal identificable como número de teléfono, dirección de correo electrónico, información de tarjetas de crédito, etc., o datos sensibles como contraseñas.
+
+#### 3. Configurar cookies (opciones recomendadas)
+
+Para almacenar la sesión en una cookie, usa la API [`cookies`](/docs/app/api-reference/functions/cookies) de Next.js. La cookie debe configurarse en el servidor e incluir las opciones recomendadas:
+
+- **HttpOnly**: Evita que JavaScript del lado del cliente acceda a la cookie.
+- **Secure**: Usa https para enviar la cookie.
+- **SameSite**: Especifica si la cookie puede enviarse con solicitudes entre sitios.
+- **Max-Age o Expires**: Elimina la cookie después de un período determinado.
+- **Path**: Define la ruta URL para la cookie.
+
+Consulta [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies) para más información sobre cada una de estas opciones.
+
+```ts filename="app/lib/session.ts" switcher
+import 'server-only'
+import { cookies } from 'next/headers'
+
+export async function createSession(userId: string) {
+ const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
+ const session = await encrypt({ userId, expiresAt })
+ const cookieStore = await cookies()
+
+ cookieStore.set('session', session, {
+ httpOnly: true,
+ secure: true,
+ expires: expiresAt,
+ sameSite: 'lax',
+ path: '/',
+ })
+}
+```
+
+```js filename="app/lib/session.js" switcher
+import 'server-only'
+import { cookies } from 'next/headers'
+
+export async function createSession(userId) {
+ const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
+ const session = await encrypt({ userId, expiresAt })
+ const cookieStore = await cookies()
+
+ cookieStore.set('session', session, {
+ httpOnly: true,
+ secure: true,
+ expires: expiresAt,
+ sameSite: 'lax',
+ path: '/',
+ })
+}
+```
+
+De vuelta en tu Acción de Servidor, puedes invocar la función `createSession()` y usar la API [`redirect()`](/docs/app/guides/redirecting) para redirigir al usuario a la página apropiada:
+
+```ts filename="app/actions/auth.ts" switcher
+import { createSession } from '@/app/lib/session'
+
+export async function signup(state: FormState, formData: FormData) {
+ // Pasos previos:
+ // 1. Validar campos del formulario
+ // 2. Preparar datos para inserción en la base de datos
+ // 3. Insertar el usuario en la base de datos o llamar a una API de biblioteca
+
+ // Pasos actuales:
+ // 4. Crear sesión de usuario
+ await createSession(user.id)
+ // 5. Redirigir al usuario
+ redirect('/profile')
+}
+```
+
+```js filename="app/actions/auth.js" switcher
+import { createSession } from '@/app/lib/session'
+
+export async function signup(state, formData) {
+ // Pasos previos:
+ // 1. Validar campos del formulario
+ // 2. Preparar datos para inserción en la base de datos
+ // 3. Insertar el usuario en la base de datos o llamar a una API de biblioteca
+
+ // Pasos actuales:
+ // 4. Crear sesión de usuario
+ await createSession(user.id)
+ // 5. Redirigir al usuario
+ redirect('/profile')
+}
+```
+
+> **Consejos**:
+>
+> - **Las cookies deben configurarse en el servidor** para evitar manipulaciones del lado del cliente.
+> - 🎥 Mira: Aprende más sobre sesiones sin estado y autenticación con Next.js → [YouTube (11 minutos)](https://www.youtube.com/watch?v=DJvM2lSPn6w).
+
+#### Actualizar (o refrescar) sesiones
+
+También puedes extender el tiempo de expiración de la sesión. Esto es útil para mantener al usuario conectado después de que acceda nuevamente a la aplicación. Por ejemplo:
+
+```ts filename="app/lib/session.ts" switcher
+import 'server-only'
+import { cookies } from 'next/headers'
+import { decrypt } from '@/app/lib/session'
+
+export async function updateSession() {
+ const session = (await cookies()).get('session')?.value
+ const payload = await decrypt(session)
+
+ if (!session || !payload) {
+ return null
+ }
+
+ const expires = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
+
+ const cookieStore = await cookies()
+ cookieStore.set('session', session, {
+ httpOnly: true,
+ secure: true,
+ expires: expires,
+ sameSite: 'lax',
+ path: '/',
+ })
+}
+```
+
+```js filename="app/lib/session.js" switcher
+import 'server-only'
+import { cookies } from 'next/headers'
+import { decrypt } from '@/app/lib/session'
+
+export async function updateSession() {
+ const session = (await cookies()).get('session')?.value
+ const payload = await decrypt(session)
+
+ if (!session || !payload) {
+ return null
+ }
+
+ const expires = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)(
+ await cookies()
+ ).set('session', session, {
+ httpOnly: true,
+ secure: true,
+ expires: expires,
+ sameSite: 'lax',
+ path: '/',
+ })
+}
+```
+
+> **Consejo:** Verifica si tu biblioteca de autenticación admite tokens de actualización, que pueden usarse para extender la sesión del usuario.
+
+#### Eliminación de la sesión
+
+Para eliminar la sesión, puede borrar la cookie:
+
+```ts filename="app/lib/session.ts" switcher
+import 'server-only'
+import { cookies } from 'next/headers'
+
+export async function deleteSession() {
+ const cookieStore = await cookies()
+ cookieStore.delete('session')
+}
+```
+
+```js filename="app/lib/session.js" switcher
+import 'server-only'
+import { cookies } from 'next/headers'
+
+export async function deleteSession() {
+ const cookieStore = await cookies()
+ cookieStore.delete('session')
+}
+```
+
+Luego puede reutilizar la función `deleteSession()` en su aplicación, por ejemplo, al cerrar sesión:
+
+```ts filename="app/actions/auth.ts" switcher
+import { cookies } from 'next/headers'
+import { deleteSession } from '@/app/lib/session'
+
+export async function logout() {
+ await deleteSession()
+ redirect('/login')
+}
+```
+
+```js filename="app/actions/auth.js" switcher
+import { cookies } from 'next/headers'
+import { deleteSession } from '@/app/lib/session'
+
+export async function logout() {
+ await deleteSession()
+ redirect('/login')
+}
+```
+
+
+
+
+
+#### Configuración y eliminación de cookies
+
+Puede usar [Rutas API](/docs/pages/building-your-application/routing/api-routes) para establecer la sesión como una cookie en el servidor:
+
+```ts filename="pages/api/login.ts" switcher
+import { serialize } from 'cookie'
+import type { NextApiRequest, NextApiResponse } from 'next'
+import { encrypt } from '@/app/lib/session'
+
+export default function handler(req: NextApiRequest, res: NextApiResponse) {
+ const sessionData = req.body
+ const encryptedSessionData = encrypt(sessionData)
+
+ const cookie = serialize('session', encryptedSessionData, {
+ httpOnly: true,
+ secure: process.env.NODE_ENV === 'production',
+ maxAge: 60 * 60 * 24 * 7, // Una semana
+ path: '/',
+ })
+ res.setHeader('Set-Cookie', cookie)
+ res.status(200).json({ message: '¡Cookie configurada exitosamente!' })
+}
+```
+
+```js filename="pages/api/login.js" switcher
+import { serialize } from 'cookie'
+import { encrypt } from '@/app/lib/session'
+
+export default function handler(req, res) {
+ const sessionData = req.body
+ const encryptedSessionData = encrypt(sessionData)
+
+ const cookie = serialize('session', encryptedSessionData, {
+ httpOnly: true,
+ secure: process.env.NODE_ENV === 'production',
+ maxAge: 60 * 60 * 24 * 7, // Una semana
+ path: '/',
+ })
+ res.setHeader('Set-Cookie', cookie)
+ res.status(200).json({ message: '¡Cookie configurada exitosamente!' })
+}
+```
+
+
+
+### Sesiones en base de datos
+
+Para crear y administrar sesiones en base de datos, deberá seguir estos pasos:
+
+1. Crear una tabla en su base de datos para almacenar sesiones y datos (o verificar si su biblioteca de autenticación maneja esto).
+2. Implementar funcionalidad para insertar, actualizar y eliminar sesiones.
+3. Cifrar el ID de sesión antes de almacenarlo en el navegador del usuario, y asegurarse de que la base de datos y la cookie estén sincronizadas (esto es opcional, pero recomendado para verificaciones optimistas de autenticación en [Middleware](#verificaciones-optimistas-con-middleware-opcional)).
+
+
+
+Por ejemplo:
+
+```ts filename="app/lib/session.ts" switcher
+import cookies from 'next/headers'
+import { db } from '@/app/lib/db'
+import { encrypt } from '@/app/lib/session'
+
+export async function createSession(id: number) {
+ const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
+
+ // 1. Crear una sesión en la base de datos
+ const data = await db
+ .insert(sessions)
+ .values({
+ userId: id,
+ expiresAt,
+ })
+ // Devolver el ID de sesión
+ .returning({ id: sessions.id })
+
+ const sessionId = data[0].id
+
+ // 2. Cifrar el ID de sesión
+ const session = await encrypt({ sessionId, expiresAt })
+
+ // 3. Almacenar la sesión en cookies para verificaciones optimistas de autenticación
+ const cookieStore = await cookies()
+ cookieStore.set('session', session, {
+ httpOnly: true,
+ secure: true,
+ expires: expiresAt,
+ sameSite: 'lax',
+ path: '/',
+ })
+}
+```
+
+```js filename="app/lib/session.js" switcher
+import cookies from 'next/headers'
+import { db } from '@/app/lib/db'
+import { encrypt } from '@/app/lib/session'
+
+export async function createSession(id) {
+ const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
+
+ // 1. Crear una sesión en la base de datos
+ const data = await db
+ .insert(sessions)
+ .values({
+ userId: id,
+ expiresAt,
+ })
+ // Devolver el ID de sesión
+ .returning({ id: sessions.id })
+
+ const sessionId = data[0].id
+
+ // 2. Cifrar el ID de sesión
+ const session = await encrypt({ sessionId, expiresAt })
+
+ // 3. Almacenar la sesión en cookies para verificaciones optimistas de autenticación
+ const cookieStore = await cookies()
+ cookieStore.set('session', session, {
+ httpOnly: true,
+ secure: true,
+ expires: expiresAt,
+ sameSite: 'lax',
+ path: '/',
+ })
+}
+```
+
+> **Consejos**:
+>
+> - Para un acceso más rápido, puede considerar agregar caché del servidor durante el tiempo de vida de la sesión. También puede mantener los datos de sesión en su base de datos principal y combinar solicitudes de datos para reducir el número de consultas.
+> - Puede optar por usar sesiones en base de datos para casos de uso más avanzados, como realizar un seguimiento de la última vez que un usuario inició sesión, el número de dispositivos activos o dar a los usuarios la capacidad de cerrar sesión en todos los dispositivos.
+
+Después de implementar la gestión de sesiones, deberá agregar lógica de autorización para controlar lo que los usuarios pueden acceder y hacer dentro de su aplicación. Continúe a la sección [Autorización](#autorización) para obtener más información.
+
+
+
+
+
+**Creación de una sesión en el servidor**:
+
+```ts filename="pages/api/create-session.ts" switcher
+import db from '../../lib/db'
+import type { NextApiRequest, NextApiResponse } from 'next'
+
+export default async function handler(
+ req: NextApiRequest,
+ res: NextApiResponse
+) {
+ try {
+ const user = req.body
+ const sessionId = generateSessionId()
+ await db.insertSession({
+ sessionId,
+ userId: user.id,
+ createdAt: new Date(),
+ })
+
+ res.status(200).json({ sessionId })
+ } catch (error) {
+ res.status(500).json({ error: 'Error interno del servidor' })
+ }
+}
+```
+
+```js filename="pages/api/create-session.js" switcher
+import db from '../../lib/db'
+
+export default async function handler(req, res) {
+ try {
+ const user = req.body
+ const sessionId = generateSessionId()
+ await db.insertSession({
+ sessionId,
+ userId: user.id,
+ createdAt: new Date(),
+ })
+
+ res.status(200).json({ sessionId })
+ } catch (error) {
+ res.status(500).json({ error: 'Error interno del servidor' })
+ }
+}
+```
+
+
+
+## Autorización
+
+Una vez que un usuario está autenticado y se crea una sesión, puede implementar autorización para controlar lo que el usuario puede acceder y hacer dentro de su aplicación.
+
+Hay dos tipos principales de verificaciones de autorización:
+
+1. **Optimistas**: Verifican si el usuario está autorizado para acceder a una ruta o realizar una acción usando los datos de sesión almacenados en la cookie. Estas verificaciones son útiles para operaciones rápidas, como mostrar/ocultar elementos de la interfaz de usuario o redirigir usuarios según permisos o roles.
+2. **Seguras**: Verifican si el usuario está autorizado para acceder a una ruta o realizar una acción usando los datos de sesión almacenados en la base de datos. Estas verificaciones son más seguras y se usan para operaciones que requieren acceso a datos sensibles o acciones.
+
+Para ambos casos, recomendamos:
+
+- Crear una [Capa de Acceso a Datos (DAL)](#creación-de-una-capa-de-acceso-a-datos-dal) para centralizar su lógica de autorización.
+- Usar [Objetos de Transferencia de Datos (DTO)](#uso-de-objetos-de-transferencia-de-datos-dto) para devolver solo los datos necesarios.
+- Opcionalmente usar [Middleware](#verificaciones-optimistas-con-middleware-opcional) para realizar verificaciones optimistas.
+
+### Verificaciones optimistas con Middleware (Opcional)
+
+Hay algunos casos donde puede querer usar [Middleware](/docs/app/building-your-application/routing/middleware) y redirigir usuarios según permisos:
+
+- Para realizar verificaciones optimistas. Dado que Middleware se ejecuta en cada ruta, es una buena manera de centralizar la lógica de redirección y pre-filtrar usuarios no autorizados.
+- Para proteger rutas estáticas que comparten datos entre usuarios (ej. contenido detrás de un muro de pago).
+
+Sin embargo, dado que Middleware se ejecuta en cada ruta, incluyendo rutas [precargadas](/docs/app/building-your-application/routing/linking-and-navigating#2-prefetching), es importante solo leer la sesión de la cookie (verificaciones optimistas), y evitar verificaciones en la base de datos para prevenir problemas de rendimiento.
+
+Por ejemplo:
+
+```tsx filename="middleware.ts" switcher
+import { NextRequest, NextResponse } from 'next/server'
+import { decrypt } from '@/app/lib/session'
+import { cookies } from 'next/headers'
+
+// 1. Especificar rutas protegidas y públicas
+const protectedRoutes = ['/dashboard']
+const publicRoutes = ['/login', '/signup', '/']
+
+export default async function middleware(req: NextRequest) {
+ // 2. Verificar si la ruta actual es protegida o pública
+ const path = req.nextUrl.pathname
+ const isProtectedRoute = protectedRoutes.includes(path)
+ const isPublicRoute = publicRoutes.includes(path)
+
+ // 3. Descifrar la sesión desde la cookie
+ const cookie = (await cookies()).get('session')?.value
+ const session = await decrypt(cookie)
+
+ // 4. Redirigir a /login si el usuario no está autenticado
+ if (isProtectedRoute && !session?.userId) {
+ return NextResponse.redirect(new URL('/login', req.nextUrl))
+ }
+
+ // 5. Redirigir a /dashboard si el usuario está autenticado
+ if (
+ isPublicRoute &&
+ session?.userId &&
+ !req.nextUrl.pathname.startsWith('/dashboard')
+ ) {
+ return NextResponse.redirect(new URL('/dashboard', req.nextUrl))
+ }
+
+ return NextResponse.next()
+}
+
+// Rutas donde Middleware no debería ejecutarse
+export const config = {
+ matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'],
+}
+```
+
+```js filename="middleware.js" switcher
+import { NextResponse } from 'next/server'
+import { decrypt } from '@/app/lib/session'
+import { cookies } from 'next/headers'
+
+// 1. Especificar rutas protegidas y públicas
+const protectedRoutes = ['/dashboard']
+const publicRoutes = ['/login', '/signup', '/']
+
+export default async function middleware(req) {
+ // 2. Verificar si la ruta actual es protegida o pública
+ const path = req.nextUrl.pathname
+ const isProtectedRoute = protectedRoutes.includes(path)
+ const isPublicRoute = publicRoutes.includes(path)
+
+ // 3. Descifrar la sesión desde la cookie
+ const cookie = (await cookies()).get('session')?.value
+ const session = await decrypt(cookie)
+
+ // 5. Redirigir a /login si el usuario no está autenticado
+ if (isProtectedRoute && !session?.userId) {
+ return NextResponse.redirect(new URL('/login', req.nextUrl))
+ }
+
+ // 6. Redirigir a /dashboard si el usuario está autenticado
+ if (
+ isPublicRoute &&
+ session?.userId &&
+ !req.nextUrl.pathname.startsWith('/dashboard')
+ ) {
+ return NextResponse.redirect(new URL('/dashboard', req.nextUrl))
+ }
+
+ return NextResponse.next()
+}
+
+// Rutas donde Middleware no debería ejecutarse
+export const config = {
+ matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'],
+}
+```
+
+Si bien Middleware puede ser útil para verificaciones iniciales, no debería ser su única línea de defensa para proteger sus datos. La mayoría de las verificaciones de seguridad deberían realizarse lo más cerca posible de su fuente de datos, consulte [Capa de Acceso a Datos](#creación-de-una-capa-de-acceso-a-datos-dal) para obtener más información.
+
+> **Consejos**:
+>
+> - En Middleware, también puede leer cookies usando `req.cookies.get('session').value`.
+> - Middleware usa el [Edge Runtime](/docs/app/api-reference/edge), verifique si su biblioteca de autenticación y gestión de sesiones son compatibles.
+> - Puede usar la propiedad `matcher` en Middleware para especificar en qué rutas debería ejecutarse Middleware. Aunque, para autenticación, se recomienda que Middleware se ejecute en todas las rutas.
+
+
+
+### Creación de una Capa de Acceso a Datos (DAL)
+
+Recomendamos crear una DAL para centralizar sus solicitudes de datos y lógica de autorización.
+
+La DAL debe incluir una función que verifique la sesión del usuario mientras interactúa con su aplicación. Como mínimo, la función debería verificar si la sesión es válida, luego redirigir o devolver la información del usuario necesaria para realizar más solicitudes.
+
+Por ejemplo, cree un archivo separado para su DAL que incluya una función `verifySession()`. Luego use la API de [cache](https://react.dev/reference/react/cache) de React para memorizar el valor de retorno de la función durante un pase de renderizado de React:
+
+```tsx filename="app/lib/dal.ts" switcher
+import 'server-only'
+
+import { cookies } from 'next/headers'
+import { decrypt } from '@/app/lib/session'
+
+export const verifySession = cache(async () => {
+ const cookie = (await cookies()).get('session')?.value
+ const session = await decrypt(cookie)
+
+ if (!session?.userId) {
+ redirect('/login')
+ }
+
+ return { isAuth: true, userId: session.userId }
+})
+```
+
+```js filename="app/lib/dal.js" switcher
+import 'server-only'
+
+import { cookies } from 'next/headers'
+import { decrypt } from '@/app/lib/session'
+
+export const verifySession = cache(async () => {
+ const cookie = (await cookies()).get('session')?.value
+ const session = await decrypt(cookie)
+
+ if (!session.userId) {
+ redirect('/login')
+ }
+
+ return { isAuth: true, userId: session.userId }
+})
+```
+
+Luego puede invocar la función `verifySession()` en sus solicitudes de datos, Acciones de Servidor, Manejadores de Ruta:
+
+```tsx filename="app/lib/dal.ts" switcher
+export const getUser = cache(async () => {
+ const session = await verifySession()
+ if (!session) return null
+
+ try {
+ const data = await db.query.users.findMany({
+ where: eq(users.id, session.userId),
+ // Devuelva explícitamente las columnas que necesita en lugar de todo el objeto de usuario
+ columns: {
+ id: true,
+ name: true,
+ email: true,
+ },
+ })
+
+ const user = data[0]
+
+ return user
+ } catch (error) {
+ console.log('Error al obtener usuario')
+ return null
+ }
+})
+```
+
+```jsx filename="app/lib/dal.js" switcher
+export const getUser = cache(async () => {
+ const session = await verifySession()
+ if (!session) return null
+
+ try {
+ const data = await db.query.users.findMany({
+ where: eq(users.id, session.userId),
+ // Devuelva explícitamente las columnas que necesita en lugar de todo el objeto de usuario
+ columns: {
+ id: true,
+ name: true,
+ email: true,
+ },
+ })
+
+ const user = data[0]
+
+ return user
+ } catch (error) {
+ console.log('Error al obtener usuario')
+ return null
+ }
+})
+```
+
+> **Consejo**:
+>
+> - Una DAL puede usarse para proteger datos obtenidos en tiempo de solicitud. Sin embargo, para rutas estáticas que comparten datos entre usuarios, los datos se obtendrán en tiempo de compilación y no en tiempo de solicitud. Use [Middleware](#verificaciones-optimistas-con-middleware-opcional) para proteger rutas estáticas.
+> - Para verificaciones seguras, puede verificar si la sesión es válida comparando el ID de sesión con su base de datos. Use la función [cache](https://react.dev/reference/react/cache) de React para evitar solicitudes duplicadas innecesarias a la base de datos durante un pase de renderizado.
+> - Puede consolidar solicitudes de datos relacionadas en una clase JavaScript que ejecute `verifySession()` antes de cualquier método.
+
+### Uso de Objetos de Transferencia de Datos (DTO)
+
+Al recuperar datos, se recomienda devolver solo la información necesaria que se utilizará en su aplicación, y no objetos completos. Por ejemplo, si está obteniendo datos de usuario, podría devolver solo el ID y el nombre del usuario, en lugar del objeto de usuario completo que podría contener contraseñas, números de teléfono, etc.
+
+Sin embargo, si no tiene control sobre la estructura de datos devuelta, o está trabajando en equipo y desea evitar que se pasen objetos completos al cliente, puede utilizar estrategias como especificar qué campos son seguros para exponer al cliente.
+
+```tsx filename="app/lib/dto.ts" switcher
+import 'server-only'
+import { getUser } from '@/app/lib/dal'
+
+function canSeeUsername(viewer: User) {
+ return true
+}
+
+function canSeePhoneNumber(viewer: User, team: string) {
+ return viewer.isAdmin || team === viewer.team
+}
+
+export async function getProfileDTO(slug: string) {
+ const data = await db.query.users.findMany({
+ where: eq(users.slug, slug),
+ // Devuelve columnas específicas aquí
+ })
+ const user = data[0]
+
+ const currentUser = await getUser(user.id)
+
+ // O devuelve solo lo específico para la consulta aquí
+ return {
+ username: canSeeUsername(currentUser) ? user.username : null,
+ phonenumber: canSeePhoneNumber(currentUser, user.team)
+ ? user.phonenumber
+ : null,
+ }
+}
+```
+
+```js filename="app/lib/dto.js" switcher
+import 'server-only'
+import { getUser } from '@/app/lib/dal'
+
+function canSeeUsername(viewer) {
+ return true
+}
+
+function canSeePhoneNumber(viewer, team) {
+ return viewer.isAdmin || team === viewer.team
+}
+
+export async function getProfileDTO(slug) {
+ const data = await db.query.users.findMany({
+ where: eq(users.slug, slug),
+ // Devuelve columnas específicas aquí
+ })
+ const user = data[0]
+
+ const currentUser = await getUser(user.id)
+
+ // O devuelve solo lo específico para la consulta aquí
+ return {
+ username: canSeeUsername(currentUser) ? user.username : null,
+ phonenumber: canSeePhoneNumber(currentUser, user.team)
+ ? user.phonenumber
+ : null,
+ }
+}
+```
+
+Al centralizar sus solicitudes de datos y la lógica de autorización en una DAL (Capa de Acceso a Datos) y utilizar DTOs, puede asegurarse de que todas las solicitudes de datos sean seguras y consistentes, facilitando el mantenimiento, auditoría y depuración a medida que su aplicación escala.
+
+> **Es bueno saberlo**:
+>
+> - Hay varias formas de definir un DTO, desde usar `toJSON()`, hasta funciones individuales como en el ejemplo anterior, o clases de JS. Dado que estos son patrones de JavaScript y no una característica de React o Next.js, recomendamos investigar para encontrar el mejor patrón para su aplicación.
+> - Aprenda más sobre las mejores prácticas de seguridad en nuestro [artículo sobre Seguridad en Next.js](/blog/security-nextjs-server-components-actions).
+
+### Componentes del Servidor
+
+Las verificaciones de autenticación en [Componentes del Servidor](/docs/app/getting-started/server-and-client-components) son útiles para el acceso basado en roles. Por ejemplo, para renderizar componentes condicionalmente según el rol del usuario:
+
+```tsx filename="app/dashboard/page.tsx" switcher
+import { verifySession } from '@/app/lib/dal'
+
+export default function Dashboard() {
+ const session = await verifySession()
+ const userRole = session?.user?.role // Asumiendo que 'role' es parte del objeto de sesión
+
+ if (userRole === 'admin') {
+ return
+ } else if (userRole === 'user') {
+ return
+ } else {
+ redirect('/login')
+ }
+}
+```
+
+```jsx filename="app/dashboard/page.jsx" switcher
+import { verifySession } from '@/app/lib/dal'
+
+export default function Dashboard() {
+ const session = await verifySession()
+ const userRole = session.role // Asumiendo que 'role' es parte del objeto de sesión
+
+ if (userRole === 'admin') {
+ return
+ } else if (userRole === 'user') {
+ return
+ } else {
+ redirect('/login')
+ }
+}
+```
+
+En el ejemplo, usamos la función `verifySession()` de nuestra DAL para verificar los roles 'admin', 'user' y no autorizados. Este patrón asegura que cada usuario interactúe solo con los componentes apropiados para su rol.
+
+### Diseños y verificaciones de autenticación
+
+Debido al [Renderizado Parcial](/docs/app/building-your-application/routing/linking-and-navigating#4-partial-rendering), tenga cuidado al realizar verificaciones en [Diseños](/docs/app/api-reference/file-conventions/layout), ya que estos no se vuelven a renderizar en la navegación, lo que significa que la sesión del usuario no se verificará en cada cambio de ruta.
+
+En su lugar, debe realizar las verificaciones cerca de su fuente de datos o del componente que se renderizará condicionalmente.
+
+Por ejemplo, considere un diseño compartido que obtiene los datos del usuario y muestra la imagen del usuario en una navegación. En lugar de hacer la verificación de autenticación en el diseño, debe obtener los datos del usuario (`getUser()`) en el diseño y hacer la verificación de autenticación en su DAL.
+
+Esto garantiza que donde sea que se llame a `getUser()` dentro de su aplicación, se realice la verificación de autenticación, y evita que los desarrolladores olviden verificar si el usuario está autorizado para acceder a los datos.
+
+```tsx filename="app/layout.tsx" switcher
+export default async function Layout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ const user = await getUser();
+
+ return (
+ // ...
+ )
+}
+```
+
+```jsx filename="app/layout.js" switcher
+export default async function Layout({ children }) {
+ const user = await getUser();
+
+ return (
+ // ...
+ )
+}
+```
+
+```ts filename="app/lib/dal.ts" switcher
+export const getUser = cache(async () => {
+ const session = await verifySession()
+ if (!session) return null
+
+ // Obtener ID de usuario de la sesión y obtener datos
+})
+```
+
+```js filename="app/lib/dal.js" switcher
+export const getUser = cache(async () => {
+ const session = await verifySession()
+ if (!session) return null
+
+ // Obtener ID de usuario de la sesión y obtener datos
+})
+```
+
+> **Es bueno saberlo:**
+>
+> - Un patrón común en SPAs es devolver `null` en un diseño o un componente de nivel superior si un usuario no está autorizado. Este patrón **no se recomienda** ya que las aplicaciones de Next.js tienen múltiples puntos de entrada, lo que no evitará que se acceda a segmentos de ruta anidados y Acciones del Servidor.
+
+### Acciones del Servidor
+
+Trate las [Acciones del Servidor](/docs/app/building-your-application/data-fetching/server-actions-and-mutations) con las mismas consideraciones de seguridad que los puntos finales de API públicos, y verifique si el usuario está autorizado para realizar una mutación.
+
+En el ejemplo a continuación, verificamos el rol del usuario antes de permitir que la acción continúe:
+
+```ts filename="app/lib/actions.ts" switcher
+'use server'
+import { verifySession } from '@/app/lib/dal'
+
+export async function serverAction(formData: FormData) {
+ const session = await verifySession()
+ const userRole = session?.user?.role
+
+ // Devuelve temprano si el usuario no está autorizado para realizar la acción
+ if (userRole !== 'admin') {
+ return null
+ }
+
+ // Procede con la acción para usuarios autorizados
+}
+```
+
+```js filename="app/lib/actions.js" switcher
+'use server'
+import { verifySession } from '@/app/lib/dal'
+
+export async function serverAction() {
+ const session = await verifySession()
+ const userRole = session.user.role
+
+ // Devuelve temprano si el usuario no está autorizado para realizar la acción
+ if (userRole !== 'admin') {
+ return null
+ }
+
+ // Procede con la acción para usuarios autorizados
+}
+```
+
+### Manejadores de Ruta
+
+Trate los [Manejadores de Ruta](/docs/app/building-your-application/routing/route-handlers) con las mismas consideraciones de seguridad que los puntos finales de API públicos, y verifique si el usuario está autorizado para acceder al Manejador de Ruta.
+
+Por ejemplo:
+
+```ts filename="app/api/route.ts" switcher
+import { verifySession } from '@/app/lib/dal'
+
+export async function GET() {
+ // Autenticación del usuario y verificación de rol
+ const session = await verifySession()
+
+ // Verifica si el usuario está autenticado
+ if (!session) {
+ // El usuario no está autenticado
+ return new Response(null, { status: 401 })
+ }
+
+ // Verifica si el usuario tiene el rol 'admin'
+ if (session.user.role !== 'admin') {
+ // El usuario está autenticado pero no tiene los permisos correctos
+ return new Response(null, { status: 403 })
+ }
+
+ // Continúa para usuarios autorizados
+}
+```
+
+```js filename="app/api/route.js" switcher
+import { verifySession } from '@/app/lib/dal'
+
+export async function GET() {
+ // Autenticación del usuario y verificación de rol
+ const session = await verifySession()
+
+ // Verifica si el usuario está autenticado
+ if (!session) {
+ // El usuario no está autenticado
+ return new Response(null, { status: 401 })
+ }
+
+ // Verifica si el usuario tiene el rol 'admin'
+ if (session.user.role !== 'admin') {
+ // El usuario está autenticado pero no tiene los permisos correctos
+ return new Response(null, { status: 403 })
+ }
+
+ // Continúa para usuarios autorizados
+}
+```
+
+El ejemplo anterior demuestra un Manejador de Ruta con una verificación de seguridad de dos niveles. Primero verifica si hay una sesión activa, y luego verifica si el usuario que inició sesión es un 'admin'.
+
+## Proveedores de Contexto
+
+El uso de proveedores de contexto para autenticación funciona debido al [entrelazado](/docs/app/getting-started/server-and-client-components#examples#interleaving-server-and-client-components). Sin embargo, el `context` de React no es compatible con los Componentes del Servidor, lo que los hace aplicables solo a Componentes del Cliente.
+
+Esto funciona, pero cualquier Componente del Servidor hijo se renderizará primero en el servidor y no tendrá acceso a los datos de sesión del proveedor de contexto:
+
+```tsx filename="app/layout.ts" switcher
+import { ContextProvider } from 'auth-lib'
+
+export default function RootLayout({ children }) {
+ return (
+
+
+ {children}
+
+
+ )
+}
+```
+
+```tsx filename="app/ui/profile.ts switcher
+'use client';
+
+import { useSession } from "auth-lib";
+
+export default function Profile() {
+ const { userId } = useSession();
+ const { data } = useSWR(`/api/user/${userId}`, fetcher)
+
+ return (
+ // ...
+ );
+}
+```
+
+```jsx filename="app/ui/profile.js switcher
+'use client';
+
+import { useSession } from "auth-lib";
+
+export default function Profile() {
+ const { userId } = useSession();
+ const { data } = useSWR(`/api/user/${userId}`, fetcher)
+
+ return (
+ // ...
+ );
+}
+```
+
+Si se necesitan datos de sesión en Componentes del Cliente (por ejemplo, para la obtención de datos del lado del cliente), use la API [`taintUniqueValue`](https://react.dev/reference/react/experimental_taintUniqueValue) de React para evitar que los datos sensibles de sesión se expongan al cliente.
+
+
+
+
+
+### Creación de una Capa de Acceso a Datos (DAL)
+
+#### Protección de Rutas de API
+
+Las Rutas de API en Next.js son esenciales para manejar la lógica del lado del servidor y la gestión de datos. Es crucial asegurar estas rutas para garantizar que solo los usuarios autorizados puedan acceder a funcionalidades específicas. Esto generalmente implica verificar el estado de autenticación del usuario y sus permisos basados en roles.
+
+Aquí hay un ejemplo de cómo asegurar una Ruta de API:
+
+```ts filename="pages/api/route.ts" switcher
+import { NextApiRequest, NextApiResponse } from 'next'
+
+export default async function handler(
+ req: NextApiRequest,
+ res: NextApiResponse
+) {
+ const session = await getSession(req)
+
+ // Verifica si el usuario está autenticado
+ if (!session) {
+ res.status(401).json({
+ error: 'El usuario no está autenticado',
+ })
+ return
+ }
+
+ // Verifica si el usuario tiene el rol 'admin'
+ if (session.user.role !== 'admin') {
+ res.status(401).json({
+ error: 'Acceso no autorizado: El usuario no tiene privilegios de administrador.',
+ })
+ return
+ }
+
+ // Procede con la ruta para usuarios autorizados
+ // ... implementación de la Ruta de API
+}
+```
+
+```js filename="pages/api/route.js" switcher
+export default async function handler(req, res) {
+ const session = await getSession(req)
+
+ // Verifica si el usuario está autenticado
+ if (!session) {
+ res.status(401).json({
+ error: 'El usuario no está autenticado',
+ })
+ return
+ }
+
+ // Verifica si el usuario tiene el rol 'admin'
+ if (session.user.role !== 'admin') {
+ res.status(401).json({
+ error: 'Acceso no autorizado: El usuario no tiene privilegios de administrador.',
+ })
+ return
+ }
+
+ // Procede con la ruta para usuarios autorizados
+ // ... implementación de la Ruta de API
+}
+```
+
+Este ejemplo demuestra una Ruta de API con una verificación de seguridad de dos niveles para autenticación y autorización. Primero verifica si hay una sesión activa, y luego verifica si el usuario que inició sesión es un 'admin'. Este enfoque garantiza un acceso seguro, limitado a usuarios autenticados y autorizados, manteniendo una seguridad robusta para el procesamiento de solicitudes.
+
+
+
+## Recursos
+
+Ahora que ha aprendido sobre autenticación en Next.js, aquí hay bibliotecas y recursos compatibles con Next.js para ayudarle a implementar autenticación segura y gestión de sesiones:
+
+### Bibliotecas de Autenticación
+
+- [Auth0](https://auth0.com/docs/quickstart/webapp/nextjs/01-login)
+- [Better Auth](https://www.better-auth.com/docs/integrations/next)
+- [Clerk](https://clerk.com/docs/quickstarts/nextjs)
+- [Kinde](https://kinde.com/docs/developer-tools/nextjs-sdk)
+- [Logto](https://docs.logto.io/quick-starts/next-app-router)
+- [NextAuth.js](https://authjs.dev/getting-started/installation?framework=next.js)
+- [Ory](https://www.ory.sh/docs/getting-started/integrate-auth/nextjs)
+- [Stack Auth](https://docs.stack-auth.com/getting-started/setup)
+- [Supabase](https://supabase.com/docs/guides/getting-started/quickstarts/nextjs)
+- [Stytch](https://stytch.com/docs/guides/quickstarts/nextjs)
+- [WorkOS](https://workos.com/docs/user-management/nextjs)
+
+### Bibliotecas de Gestión de Sesiones
+
+- [Iron Session](https://github.com/vvo/iron-session)
+- [Jose](https://github.com/panva/jose)
+
+## Lectura Adicional
+
+Para continuar aprendiendo sobre autenticación y seguridad, consulte los siguientes recursos:
+
+- [Cómo pensar sobre seguridad en Next.js](/blog/security-nextjs-server-components-actions)
+- [Entendiendo los Ataques XSS](https://vercel.com/guides/understanding-xss-attacks)
+- [Entendiendo los Ataques CSRF](https://vercel.com/guides/understanding-csrf-attacks)
+- [The Copenhagen Book](https://thecopenhagenbook.com/)
diff --git a/apps/docs/content/es/docs/01-app/02-guides/ci-build-caching.mdx b/apps/docs/content/es/docs/01-app/02-guides/ci-build-caching.mdx
new file mode 100644
index 00000000..feab6c05
--- /dev/null
+++ b/apps/docs/content/es/docs/01-app/02-guides/ci-build-caching.mdx
@@ -0,0 +1,171 @@
+---
+source-updated-at: 2025-05-16T04:52:11.000Z
+translation-updated-at: 2025-06-02T20:01:25.926Z
+title: Cómo configurar el almacenamiento en caché para compilaciones de Integración Continua (CI)
+nav_title: Almacenamiento en caché en CI
+description: Aprenda a configurar CI para almacenar en caché las compilaciones de Next.js
+---
+
+Para mejorar el rendimiento de las compilaciones, Next.js guarda una caché en `.next/cache` que se comparte entre compilaciones.
+
+Para aprovechar esta caché en entornos de Integración Continua (CI), su flujo de trabajo de CI deberá configurarse para persistir correctamente la caché entre compilaciones.
+
+> Si su CI no está configurado para persistir `.next/cache` entre compilaciones, puede que vea un error de [No se detectó caché](/docs/messages/no-cache).
+
+Aquí hay algunas configuraciones de ejemplo para proveedores comunes de CI:
+
+## Vercel
+
+El almacenamiento en caché de Next.js se configura automáticamente. No es necesario realizar ninguna acción. Si está usando Turborepo en Vercel, [aprenda más aquí](https://vercel.com/docs/monorepos/turborepo).
+
+## CircleCI
+
+Edite su paso `save_cache` en `.circleci/config.yml` para incluir `.next/cache`:
+
+```yaml
+steps:
+ - save_cache:
+ key: dependency-cache-{{ checksum "yarn.lock" }}
+ paths:
+ - ./node_modules
+ - ./.next/cache
+```
+
+Si no tiene una clave `save_cache`, siga la [documentación de CircleCI sobre configuración de caché](https://circleci.com/docs/2.0/caching/).
+
+## Travis CI
+
+Agregue o combine lo siguiente en su `.travis.yml`:
+
+```yaml
+cache:
+ directories:
+ - $HOME/.cache/yarn
+ - node_modules
+ - .next/cache
+```
+
+## GitLab CI
+
+Agregue o combine lo siguiente en su `.gitlab-ci.yml`:
+
+```yaml
+cache:
+ key: ${CI_COMMIT_REF_SLUG}
+ paths:
+ - node_modules/
+ - .next/cache/
+```
+
+## Netlify CI
+
+Utilice [Netlify Plugins](https://www.netlify.com/products/build/plugins/) con [`@netlify/plugin-nextjs`](https://www.npmjs.com/package/@netlify/plugin-nextjs).
+
+## AWS CodeBuild
+
+Agregue (o combine) lo siguiente a su `buildspec.yml`:
+
+```yaml
+cache:
+ paths:
+ - 'node_modules/**/*' # Almacena `node_modules` para `yarn` o `npm i` más rápido
+ - '.next/cache/**/*' # Almacena Next.js para recompilaciones más rápidas
+```
+
+## GitHub Actions
+
+Usando [actions/cache](https://github.com/actions/cache) de GitHub, agregue el siguiente paso en su archivo de flujo de trabajo:
+
+```yaml
+uses: actions/cache@v4
+with:
+ # Vea aquí para almacenamiento con `yarn`, `bun` u otros gestores de paquetes https://github.com/actions/cache/blob/main/examples.md o puede usar almacenamiento con actions/setup-node https://github.com/actions/setup-node
+ path: |
+ ~/.npm
+ ${{ github.workspace }}/.next/cache
+ # Genera una nueva caché cuando cambian paquetes o archivos fuente.
+ key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx') }}
+ # Si los archivos fuente cambiaron pero no los paquetes, recompila desde una caché previa.
+ restore-keys: |
+ ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-
+```
+
+## Bitbucket Pipelines
+
+Agregue o combine lo siguiente en su `bitbucket-pipelines.yml` al nivel superior (mismo nivel que `pipelines`):
+
+```yaml
+definitions:
+ caches:
+ nextcache: .next/cache
+```
+
+Luego refiéralo en la sección `caches` del `step` de su pipeline:
+
+```yaml
+- step:
+ name: your_step_name
+ caches:
+ - node
+ - nextcache
+```
+
+## Heroku
+
+Usando el [almacenamiento personalizado](https://devcenter.heroku.com/articles/nodejs-support#custom-caching) de Heroku, agregue un arreglo `cacheDirectories` en su package.json de nivel superior:
+
+```javascript
+"cacheDirectories": [".next/cache"]
+```
+
+## Azure Pipelines
+
+Usando la [tarea Cache](https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/utility/cache) de Azure Pipelines, agregue la siguiente tarea a su archivo yaml del pipeline antes de la tarea que ejecuta `next build`:
+
+```yaml
+- task: Cache@2
+ displayName: 'Cache .next/cache'
+ inputs:
+ key: next | $(Agent.OS) | yarn.lock
+ path: '$(System.DefaultWorkingDirectory)/.next/cache'
+```
+
+## Jenkins (Pipeline)
+
+Usando el plugin [Job Cacher](https://www.jenkins.io/doc/pipeline/steps/jobcacher/) de Jenkins, agregue el siguiente paso de compilación a su `Jenkinsfile` donde normalmente ejecutaría `next build` o `npm install`:
+
+```yaml
+stage("Restore npm packages") {
+ steps {
+ // Escribe archivo de bloqueo para caché basado en hash GIT_COMMIT
+ writeFile file: "next-lock.cache", text: "$GIT_COMMIT"
+
+ cache(caches: [
+ arbitraryFileCache(
+ path: "node_modules",
+ includes: "**/*",
+ cacheValidityDecidingFile: "package-lock.json"
+ )
+ ]) {
+ sh "npm install"
+ }
+ }
+}
+stage("Build") {
+ steps {
+ // Escribe archivo de bloqueo para caché basado en hash GIT_COMMIT
+ writeFile file: "next-lock.cache", text: "$GIT_COMMIT"
+
+ cache(caches: [
+ arbitraryFileCache(
+ path: ".next/cache",
+ includes: "**/*",
+ cacheValidityDecidingFile: "next-lock.cache"
+ )
+ ]) {
+ // aka `next build`
+ sh "npm run build"
+ }
+ }
+}
+```
\ No newline at end of file
diff --git a/apps/docs/content/es/docs/01-app/02-guides/content-security-policy.mdx b/apps/docs/content/es/docs/01-app/02-guides/content-security-policy.mdx
new file mode 100644
index 00000000..e6b83f7f
--- /dev/null
+++ b/apps/docs/content/es/docs/01-app/02-guides/content-security-policy.mdx
@@ -0,0 +1,299 @@
+---
+source-updated-at: 2025-05-19T22:31:51.000Z
+translation-updated-at: 2025-06-02T20:01:48.546Z
+title: Cómo configurar una Política de Seguridad de Contenido (CSP) para tu aplicación Next.js
+nav_title: Política de Seguridad de Contenido
+description: Aprende a configurar una Política de Seguridad de Contenido (CSP) para tu aplicación Next.js.
+related:
+ links:
+ - app/building-your-application/routing/middleware
+ - app/api-reference/functions/headers
+---
+
+{/* El contenido de este documento se comparte entre el enrutador de app y pages. Puedes usar el componente `Contenido ` para agregar contenido específico del enrutador Pages. Cualquier contenido compartido no debe estar envuelto en un componente. */}
+
+[La Política de Seguridad de Contenido (CSP)](https://developer.mozilla.org/docs/Web/HTTP/CSP) es importante para proteger tu aplicación Next.js contra diversas amenazas de seguridad como cross-site scripting (XSS), clickjacking y otros ataques de inyección de código.
+
+Al usar CSP, los desarrolladores pueden especificar qué orígenes son permitidos para fuentes de contenido, scripts, hojas de estilo, imágenes, fuentes, objetos, medios (audio, video), iframes y más.
+
+
+ Ejemplos
+
+- [CSP estricto](https://github.com/vercel/next.js/tree/canary/examples/with-strict-csp)
+
+
+
+## Nonces
+
+Un [nonce](https://developer.mozilla.org/docs/Web/HTML/Global_attributes/nonce) es una cadena única y aleatoria de caracteres creada para un uso único. Se utiliza junto con CSP para permitir selectivamente que ciertos scripts o estilos en línea se ejecuten, evitando las directivas estrictas de CSP.
+
+### ¿Por qué usar un nonce?
+
+Aunque las CSP están diseñadas para bloquear scripts maliciosos, hay escenarios legítimos donde los scripts en línea son necesarios. En tales casos, los nonces ofrecen una forma de permitir que estos scripts se ejecuten si tienen el nonce correcto.
+
+### Agregar un nonce con Middleware
+
+[Middleware](/docs/app/building-your-application/routing/middleware) te permite agregar encabezados y generar nonces antes de que la página se renderice.
+
+Cada vez que se visualiza una página, se debe generar un nuevo nonce. Esto significa que **debes usar renderizado dinámico para agregar nonces**.
+
+Por ejemplo:
+
+```ts filename="middleware.ts" switcher
+import { NextRequest, NextResponse } from 'next/server'
+
+export function middleware(request: NextRequest) {
+ const nonce = Buffer.from(crypto.randomUUID()).toString('base64')
+ const cspHeader = `
+ default-src 'self';
+ script-src 'self' 'nonce-${nonce}' 'strict-dynamic';
+ style-src 'self' 'nonce-${nonce}';
+ img-src 'self' blob: data:;
+ font-src 'self';
+ object-src 'none';
+ base-uri 'self';
+ form-action 'self';
+ frame-ancestors 'none';
+ upgrade-insecure-requests;
+`
+ // Reemplazar saltos de línea y espacios
+ const contentSecurityPolicyHeaderValue = cspHeader
+ .replace(/\s{2,}/g, ' ')
+ .trim()
+
+ const requestHeaders = new Headers(request.headers)
+ requestHeaders.set('x-nonce', nonce)
+
+ requestHeaders.set(
+ 'Content-Security-Policy',
+ contentSecurityPolicyHeaderValue
+ )
+
+ const response = NextResponse.next({
+ request: {
+ headers: requestHeaders,
+ },
+ })
+ response.headers.set(
+ 'Content-Security-Policy',
+ contentSecurityPolicyHeaderValue
+ )
+
+ return response
+}
+```
+
+```js filename="middleware.js" switcher
+import { NextResponse } from 'next/server'
+
+export function middleware(request) {
+ const nonce = Buffer.from(crypto.randomUUID()).toString('base64')
+ const cspHeader = `
+ default-src 'self';
+ script-src 'self' 'nonce-${nonce}' 'strict-dynamic';
+ style-src 'self' 'nonce-${nonce}';
+ img-src 'self' blob: data:;
+ font-src 'self';
+ object-src 'none';
+ base-uri 'self';
+ form-action 'self';
+ frame-ancestors 'none';
+ upgrade-insecure-requests;
+`
+ // Reemplazar saltos de línea y espacios
+ const contentSecurityPolicyHeaderValue = cspHeader
+ .replace(/\s{2,}/g, ' ')
+ .trim()
+
+ const requestHeaders = new Headers(request.headers)
+ requestHeaders.set('x-nonce', nonce)
+ requestHeaders.set(
+ 'Content-Security-Policy',
+ contentSecurityPolicyHeaderValue
+ )
+
+ const response = NextResponse.next({
+ request: {
+ headers: requestHeaders,
+ },
+ })
+ response.headers.set(
+ 'Content-Security-Policy',
+ contentSecurityPolicyHeaderValue
+ )
+
+ return response
+}
+```
+
+Por defecto, Middleware se ejecuta en todas las solicitudes. Puedes filtrar Middleware para que se ejecute en rutas específicas usando un [`matcher`](/docs/app/building-your-application/routing/middleware#matcher).
+
+Recomendamos ignorar las coincidencias de prefetch (de `next/link`) y los recursos estáticos que no necesitan el encabezado CSP.
+
+```ts filename="middleware.ts" switcher
+export const config = {
+ matcher: [
+ /*
+ * Coincidir con todas las rutas excepto las que comienzan con:
+ * - api (rutas API)
+ * - _next/static (archivos estáticos)
+ * - _next/image (archivos de optimización de imágenes)
+ * - favicon.ico (archivo de favicon)
+ */
+ {
+ source: '/((?!api|_next/static|_next/image|favicon.ico).*)',
+ missing: [
+ { type: 'header', key: 'next-router-prefetch' },
+ { type: 'header', key: 'purpose', value: 'prefetch' },
+ ],
+ },
+ ],
+}
+```
+
+```js filename="middleware.js" switcher
+export const config = {
+ matcher: [
+ /*
+ * Coincidir con todas las rutas excepto las que comienzan con:
+ * - api (rutas API)
+ * - _next/static (archivos estáticos)
+ * - _next/image (archivos de optimización de imágenes)
+ * - favicon.ico (archivo de favicon)
+ */
+ {
+ source: '/((?!api|_next/static|_next/image|favicon.ico).*)',
+ missing: [
+ { type: 'header', key: 'next-router-prefetch' },
+ { type: 'header', key: 'purpose', value: 'prefetch' },
+ ],
+ },
+ ],
+}
+```
+
+### Leyendo el nonce
+
+
+ Puedes proporcionar el nonce a tu página usando
+ [`getServerSideProps`](/docs/pages/building-your-application/data-fetching/get-server-side-props):
+
+```tsx filename="pages/index.tsx" switcher
+import Script from 'next/script'
+
+import type { GetServerSideProps } from 'next'
+
+export default function Page({ nonce }) {
+ return (
+
+ )
+}
+
+export const getServerSideProps: GetServerSideProps = async ({ req }) => {
+ const nonce = req.headers['x-nonce']
+ return { props: { nonce } }
+}
+```
+
+```jsx filename="pages/index.jsx" switcher
+import Script from 'next/script'
+export default function Page({ nonce }) {
+ return (
+
+ )
+}
+
+export async function getServerSideProps({ req }) {
+ const nonce = req.headers['x-nonce']
+ return { props: { nonce } }
+}
+```
+
+
+
+
+
+Puedes leer el nonce desde un [Componente de Servidor](/docs/app/getting-started/server-and-client-components) usando [`headers`](/docs/app/api-reference/functions/headers):
+
+```tsx filename="app/page.tsx" switcher
+import { headers } from 'next/headers'
+import Script from 'next/script'
+
+export default async function Page() {
+ const nonce = (await headers()).get('x-nonce')
+
+ return (
+
+ )
+}
+```
+
+```jsx filename="app/page.jsx" switcher
+import { headers } from 'next/headers'
+import Script from 'next/script'
+
+export default async function Page() {
+ const nonce = (await headers()).get('x-nonce')
+
+ return (
+
+ )
+}
+```
+
+
+
+## Sin Nonces
+
+Para aplicaciones que no requieren nonces, puedes configurar el encabezado CSP directamente en tu archivo [`next.config.js`](/docs/app/api-reference/config/next-config-js):
+
+```js filename="next.config.js"
+const cspHeader = `
+ default-src 'self';
+ script-src 'self' 'unsafe-eval' 'unsafe-inline';
+ style-src 'self' 'unsafe-inline';
+ img-src 'self' blob: data:;
+ font-src 'self';
+ object-src 'none';
+ base-uri 'self';
+ form-action 'self';
+ frame-ancestors 'none';
+ upgrade-insecure-requests;
+`
+
+module.exports = {
+ async headers() {
+ return [
+ {
+ source: '/(.*)',
+ headers: [
+ {
+ key: 'Content-Security-Policy',
+ value: cspHeader.replace(/\n/g, ''),
+ },
+ ],
+ },
+ ]
+ },
+}
+```
+
+## Historial de versiones
+
+Recomendamos usar `v13.4.20+` de Next.js para manejar y aplicar correctamente los nonces.
\ No newline at end of file
diff --git a/apps/docs/content/es/docs/01-app/02-guides/css-in-js.mdx b/apps/docs/content/es/docs/01-app/02-guides/css-in-js.mdx
new file mode 100644
index 00000000..29cf4d8d
--- /dev/null
+++ b/apps/docs/content/es/docs/01-app/02-guides/css-in-js.mdx
@@ -0,0 +1,324 @@
+---
+source-updated-at: 2025-06-01T01:32:20.000Z
+translation-updated-at: 2025-06-02T20:02:10.220Z
+title: Cómo usar bibliotecas CSS-in-JS
+nav_title: CSS-in-JS
+description: Uso de bibliotecas CSS-in-JS con Next.js
+---
+
+{/* El contenido de este documento se comparte entre el enrutador de app y pages. Puedes usar el componente `Contenido ` para agregar contenido específico del enrutador de Pages. Cualquier contenido compartido no debe estar envuelto en un componente. */}
+
+
+
+> **Advertencia:** El uso de CSS-in-JS con características más nuevas de React como Componentes del Servidor y Streaming requiere que los autores de bibliotecas admitan la última versión de React, incluyendo [renderizado concurrente](https://react.dev/blog/2022/03/29/react-v18#what-is-concurrent-react).
+
+Las siguientes bibliotecas son compatibles en Componentes Cliente en el directorio `app` (orden alfabético):
+
+- [`ant-design`](https://ant.design/docs/react/use-with-next#using-app-router)
+- [`chakra-ui`](https://chakra-ui.com/getting-started/nextjs-app-guide)
+- [`@fluentui/react-components`](https://react.fluentui.dev/?path=/docs/concepts-developer-server-side-rendering-next-js-appdir-setup--page)
+- [`kuma-ui`](https://kuma-ui.com)
+- [`@mui/material`](https://mui.com/material-ui/guides/next-js-app-router/)
+- [`@mui/joy`](https://mui.com/joy-ui/integrations/next-js-app-router/)
+- [`pandacss`](https://panda-css.com)
+- [`styled-jsx`](#styled-jsx)
+- [`styled-components`](#styled-components)
+- [`stylex`](https://stylexjs.com)
+- [`tamagui`](https://tamagui.dev/docs/guides/next-js#server-components)
+- [`tss-react`](https://tss-react.dev/)
+- [`vanilla-extract`](https://vanilla-extract.style)
+
+Las siguientes están trabajando actualmente en la compatibilidad:
+
+- [`emotion`](https://github.com/emotion-js/emotion/issues/2928)
+
+> **Es bueno saberlo**: Estamos probando diferentes bibliotecas CSS-in-JS y agregaremos más ejemplos para bibliotecas que admitan características de React 18 y/o el directorio `app`.
+
+## Configurar CSS-in-JS en `app`
+
+Configurar CSS-in-JS es un proceso de tres pasos que incluye:
+
+1. Un **registro de estilos** para recolectar todas las reglas CSS en un renderizado.
+2. El nuevo hook `useServerInsertedHTML` para inyectar reglas antes de cualquier contenido que pueda usarlas.
+3. Un Componente Cliente que envuelve tu aplicación con el registro de estilos durante el renderizado inicial del lado del servidor.
+
+### `styled-jsx`
+
+Usar `styled-jsx` en Componentes Cliente requiere usar `v5.1.0`. Primero, crea un nuevo registro:
+
+```tsx filename="app/registry.tsx" switcher
+'use client'
+
+import React, { useState } from 'react'
+import { useServerInsertedHTML } from 'next/navigation'
+import { StyleRegistry, createStyleRegistry } from 'styled-jsx'
+
+export default function StyledJsxRegistry({
+ children,
+}: {
+ children: React.ReactNode
+}) {
+ // Solo crea la hoja de estilos una vez con estado inicial perezoso
+ // x-ref: https://reactjs.org/docs/hooks-reference.html#lazy-initial-state
+ const [jsxStyleRegistry] = useState(() => createStyleRegistry())
+
+ useServerInsertedHTML(() => {
+ const styles = jsxStyleRegistry.styles()
+ jsxStyleRegistry.flush()
+ return <>{styles}>
+ })
+
+ return {children}
+}
+```
+
+```jsx filename="app/registry.js" switcher
+'use client'
+
+import React, { useState } from 'react'
+import { useServerInsertedHTML } from 'next/navigation'
+import { StyleRegistry, createStyleRegistry } from 'styled-jsx'
+
+export default function StyledJsxRegistry({ children }) {
+ // Solo crea la hoja de estilos una vez con estado inicial perezoso
+ // x-ref: https://reactjs.org/docs/hooks-reference.html#lazy-initial-state
+ const [jsxStyleRegistry] = useState(() => createStyleRegistry())
+
+ useServerInsertedHTML(() => {
+ const styles = jsxStyleRegistry.styles()
+ jsxStyleRegistry.flush()
+ return <>{styles}>
+ })
+
+ return {children}
+}
+```
+
+Luego, envuelve tu [layout raíz](/docs/app/api-reference/file-conventions/layout#root-layout) con el registro:
+
+```tsx filename="app/layout.tsx" switcher
+import StyledJsxRegistry from './registry'
+
+export default function RootLayout({
+ children,
+}: {
+ children: React.ReactNode
+}) {
+ return (
+
+
+ {children}
+
+
+ )
+}
+```
+
+```jsx filename="app/layout.js" switcher
+import StyledJsxRegistry from './registry'
+
+export default function RootLayout({ children }) {
+ return (
+
+
+ {children}
+
+
+ )
+}
+```
+
+[Ver un ejemplo aquí](https://github.com/vercel/app-playground/tree/main/app/styling/styled-jsx).
+
+### Styled Components
+
+A continuación se muestra un ejemplo de cómo configurar `styled-components@6` o superior:
+
+Primero, habilita styled-components en `next.config.js`.
+
+```js filename="next.config.js"
+module.exports = {
+ compiler: {
+ styledComponents: true,
+ },
+}
+```
+
+Luego, usa la API de `styled-components` para crear un componente de registro global que recolecte todas las reglas de estilo CSS generadas durante un renderizado, y una función para devolver esas reglas. Luego usa el hook `useServerInsertedHTML` para inyectar los estilos recolectados en el registro dentro de la etiqueta HTML `` en el layout raíz.
+
+```tsx filename="lib/registry.tsx" switcher
+'use client'
+
+import React, { useState } from 'react'
+import { useServerInsertedHTML } from 'next/navigation'
+import { ServerStyleSheet, StyleSheetManager } from 'styled-components'
+
+export default function StyledComponentsRegistry({
+ children,
+}: {
+ children: React.ReactNode
+}) {
+ // Solo crea la hoja de estilos una vez con estado inicial perezoso
+ // x-ref: https://reactjs.org/docs/hooks-reference.html#lazy-initial-state
+ const [styledComponentsStyleSheet] = useState(() => new ServerStyleSheet())
+
+ useServerInsertedHTML(() => {
+ const styles = styledComponentsStyleSheet.getStyleElement()
+ styledComponentsStyleSheet.instance.clearTag()
+ return <>{styles}>
+ })
+
+ if (typeof window !== 'undefined') return <>{children}>
+
+ return (
+
+ {children}
+
+ )
+}
+```
+
+```jsx filename="lib/registry.js" switcher
+'use client'
+
+import React, { useState } from 'react'
+import { useServerInsertedHTML } from 'next/navigation'
+import { ServerStyleSheet, StyleSheetManager } from 'styled-components'
+
+export default function StyledComponentsRegistry({ children }) {
+ // Solo crea la hoja de estilos una vez con estado inicial perezoso
+ // x-ref: https://reactjs.org/docs/hooks-reference.html#lazy-initial-state
+ const [styledComponentsStyleSheet] = useState(() => new ServerStyleSheet())
+
+ useServerInsertedHTML(() => {
+ const styles = styledComponentsStyleSheet.getStyleElement()
+ styledComponentsStyleSheet.instance.clearTag()
+ return <>{styles}>
+ })
+
+ if (typeof window !== 'undefined') return <>{children}>
+
+ return (
+
+ {children}
+
+ )
+}
+```
+
+Envuelve los `children` del layout raíz con el componente de registro de estilos:
+
+```tsx filename="app/layout.tsx" switcher
+import StyledComponentsRegistry from './lib/registry'
+
+export default function RootLayout({
+ children,
+}: {
+ children: React.ReactNode
+}) {
+ return (
+
+
+ {children}
+
+
+ )
+}
+```
+
+```jsx filename="app/layout.js" switcher
+import StyledComponentsRegistry from './lib/registry'
+
+export default function RootLayout({ children }) {
+ return (
+
+
+ {children}
+
+
+ )
+}
+```
+
+[Ver un ejemplo aquí](https://github.com/vercel/app-playground/tree/main/app/styling/styled-components).
+
+> **Es bueno saberlo**:
+>
+> - Durante el renderizado del servidor, los estilos se extraerán a un registro global y se enviarán al `` de tu HTML. Esto asegura que las reglas de estilo se coloquen antes de cualquier contenido que pueda usarlas. En el futuro, podríamos usar una próxima característica de React para determinar dónde inyectar los estilos.
+> - Durante el streaming, los estilos de cada fragmento se recolectarán y agregarán a los estilos existentes. Después de que se complete la hidratación del lado del cliente, `styled-components` tomará el control como de costumbre e inyectará cualquier estilo dinámico adicional.
+> - Usamos específicamente un Componente Cliente en el nivel superior del árbol para el registro de estilos porque es más eficiente extraer reglas CSS de esta manera. Evita regenerar estilos en renderizados posteriores del servidor y evita que se envíen en la carga útil del Componente del Servidor.
+> - Para casos de uso avanzados donde necesites configurar propiedades individuales de la compilación de styled-components, puedes leer nuestra [referencia de API de Next.js para styled-components](/docs/architecture/nextjs-compiler#styled-components) para aprender más.
+
+
+
+
+
+
+ Ejemplos
+
+- [Styled JSX](https://github.com/vercel/next.js/tree/canary/examples/with-styled-jsx)
+- [Styled Components](https://github.com/vercel/next.js/tree/canary/examples/with-styled-components)
+- [Emotion](https://github.com/vercel/next.js/tree/canary/examples/with-emotion)
+- [Linaria](https://github.com/vercel/next.js/tree/canary/examples/with-linaria)
+- [Styletron](https://github.com/vercel/next.js/tree/canary/examples/with-styletron)
+- [Cxs](https://github.com/vercel/next.js/tree/canary/examples/with-cxs)
+- [Fela](https://github.com/vercel/next.js/tree/canary/examples/with-fela)
+- [Stitches](https://github.com/vercel/next.js/tree/canary/examples/with-stitches)
+
+
+
+Es posible usar cualquier solución CSS-in-JS existente. La más simple son los estilos en línea:
+
+```jsx
+function HiThere() {
+ return hola
+}
+
+export default HiThere
+```
+
+Incluimos [styled-jsx](https://github.com/vercel/styled-jsx) para proporcionar soporte para CSS de ámbito aislado.
+El objetivo es admitir "CSS sombra" similar a los Web Components, que desafortunadamente [no admiten renderizado del servidor y son solo JS](https://github.com/w3c/webcomponents/issues/71).
+
+Consulta los ejemplos anteriores para otras soluciones populares de CSS-in-JS (como Styled Components).
+
+Un componente que usa `styled-jsx` se ve así:
+
+```jsx
+function HelloWorld() {
+ return (
+
+ Hola mundo
+
¡de ámbito!
+
+
+
+ )
+}
+
+export default HelloWorld
+```
+
+Consulta la [documentación de styled-jsx](https://github.com/vercel/styled-jsx) para más ejemplos.
+
+### Deshabilitar JavaScript
+
+Sí, si deshabilitas JavaScript, el CSS seguirá cargándose en la compilación de producción (`next start`). Durante el desarrollo, requerimos que JavaScript esté habilitado para proporcionar la mejor experiencia de desarrollador con [Fast Refresh](https://nextjs.org/blog/next-9-4#fast-refresh).
+
+
\ No newline at end of file
diff --git a/apps/docs/content/es/docs/01-app/02-guides/custom-server.mdx b/apps/docs/content/es/docs/01-app/02-guides/custom-server.mdx
new file mode 100644
index 00000000..bd6e1486
--- /dev/null
+++ b/apps/docs/content/es/docs/01-app/02-guides/custom-server.mdx
@@ -0,0 +1,123 @@
+---
+source-updated-at: 2025-05-16T04:52:11.000Z
+translation-updated-at: 2025-06-02T20:01:17.721Z
+title: Cómo configurar un servidor personalizado en Next.js
+nav_title: Servidor Personalizado
+description: Inicie una aplicación Next.js de manera programática utilizando un servidor personalizado.
+---
+
+{/* El contenido de este documento se comparte entre el enrutador de la aplicación y el de páginas. Puede usar el componente `Contenido ` para agregar contenido específico del Enrutador de Páginas. Cualquier contenido compartido no debe estar envuelto en un componente. */}
+
+Next.js incluye su propio servidor con `next start` por defecto. Si tiene un backend existente, aún puede usarlo con Next.js (esto no es un servidor personalizado). Un servidor personalizado de Next.js le permite iniciar un servidor de manera programática para patrones personalizados. La mayoría de las veces no necesitará este enfoque. Sin embargo, está disponible si necesita desacoplarlo.
+
+> **Es bueno saberlo**:
+>
+> - Antes de decidir usar un servidor personalizado, tenga en cuenta que solo debe usarse cuando el enrutador integrado de Next.js no pueda satisfacer los requisitos de su aplicación. Un servidor personalizado eliminará optimizaciones de rendimiento importantes, como la **[Optimización Estática Automática](/docs/pages/building-your-application/rendering/automatic-static-optimization).**
+> - Cuando se utiliza el modo de salida independiente, no rastrea archivos de servidor personalizados. Este modo genera un archivo `server.js` mínimo separado. Estos no pueden usarse juntos.
+
+Consulte el [siguiente ejemplo](https://github.com/vercel/next.js/tree/canary/examples/custom-server) de un servidor personalizado:
+
+```ts filename="server.ts" switcher
+import { createServer } from 'http'
+import { parse } from 'url'
+import next from 'next'
+
+const port = parseInt(process.env.PORT || '3000', 10)
+const dev = process.env.NODE_ENV !== 'production'
+const app = next({ dev })
+const handle = app.getRequestHandler()
+
+app.prepare().then(() => {
+ createServer((req, res) => {
+ const parsedUrl = parse(req.url!, true)
+ handle(req, res, parsedUrl)
+ }).listen(port)
+
+ console.log(
+ `> Server listening at http://localhost:${port} as ${
+ dev ? 'development' : process.env.NODE_ENV
+ }`
+ )
+})
+```
+
+```js filename="server.js" switcher
+import { createServer } from 'http'
+import { parse } from 'url'
+import next from 'next'
+
+const port = parseInt(process.env.PORT || '3000', 10)
+const dev = process.env.NODE_ENV !== 'production'
+const app = next({ dev })
+const handle = app.getRequestHandler()
+
+app.prepare().then(() => {
+ createServer((req, res) => {
+ const parsedUrl = parse(req.url, true)
+ handle(req, res, parsedUrl)
+ }).listen(port)
+
+ console.log(
+ `> Server listening at http://localhost:${port} as ${
+ dev ? 'development' : process.env.NODE_ENV
+ }`
+ )
+})
+```
+
+> `server.js` no pasa por el proceso de compilación o empaquetado de Next.js. Asegúrese de que la sintaxis y el código fuente que requiere este archivo sean compatibles con la versión actual de Node.js que está utilizando. [Vea un ejemplo](https://github.com/vercel/next.js/tree/canary/examples/custom-server).
+
+Para ejecutar el servidor personalizado, deberá actualizar los `scripts` en `package.json` de la siguiente manera:
+
+```json filename="package.json"
+{
+ "scripts": {
+ "dev": "node server.js",
+ "build": "next build",
+ "start": "NODE_ENV=production node server.js"
+ }
+}
+```
+
+Alternativamente, puede configurar `nodemon` ([ejemplo](https://github.com/vercel/next.js/tree/canary/examples/custom-server)). El servidor personalizado utiliza la siguiente importación para conectar el servidor con la aplicación Next.js:
+
+```js
+import next from 'next'
+
+const app = next({})
+```
+
+La importación `next` anterior es una función que recibe un objeto con las siguientes opciones:
+
+| Opción | Tipo | Descripción |
+| ------------ | ------------------ | ----------------------------------------------------------------------------------- |
+| `conf` | `Object` | El mismo objeto que usaría en `next.config.js`. Por defecto es `{}` |
+| `dev` | `Boolean` | (_Opcional_) Si se debe iniciar Next.js en modo de desarrollo. Por defecto es `false` |
+| `dir` | `String` | (_Opcional_) Ubicación del proyecto Next.js. Por defecto es `'.'` |
+| `quiet` | `Boolean` | (_Opcional_) Oculta mensajes de error que contienen información del servidor. Por defecto es `false` |
+| `hostname` | `String` | (_Opcional_) El nombre de host detrás del cual se ejecuta el servidor |
+| `port` | `Number` | (_Opcional_) El puerto detrás del cual se ejecuta el servidor |
+| `httpServer` | `node:http#Server` | (_Opcional_) El servidor HTTP detrás del cual se ejecuta Next.js |
+| `turbo` | `Boolean` | (_Opcional_) Habilita Turbopack |
+
+La `app` devuelta puede usarse para que Next.js maneje las solicitudes según sea necesario.
+
+
+
+## Deshabilitar el enrutamiento del sistema de archivos
+
+Por defecto, `Next` servirá cada archivo en la carpeta `pages` bajo un nombre de ruta que coincida con el nombre del archivo. Si su proyecto utiliza un servidor personalizado, este comportamiento puede resultar en que el mismo contenido se sirva desde múltiples rutas, lo que puede presentar problemas con SEO y UX.
+
+Para deshabilitar este comportamiento y evitar el enrutamiento basado en archivos en `pages`, abra `next.config.js` y deshabilite la configuración `useFileSystemPublicRoutes`:
+
+```js filename="next.config.js"
+module.exports = {
+ useFileSystemPublicRoutes: false,
+}
+```
+
+> Nota: `useFileSystemPublicRoutes` deshabilita las rutas de nombres de archivo desde SSR; el enrutamiento del lado del cliente aún puede acceder a esas rutas. Al usar esta opción, debe protegerse contra la navegación a rutas que no desee programáticamente.
+
+> También puede configurar el enrutador del lado del cliente para prohibir redirecciones del lado del cliente a rutas de nombres de archivo; para eso, consulte [`router.beforePopState`](/docs/pages/api-reference/functions/use-router#routerbeforepopstate).
+
+
\ No newline at end of file
diff --git a/apps/docs/content/es/docs/01-app/02-guides/debugging.mdx b/apps/docs/content/es/docs/01-app/02-guides/debugging.mdx
new file mode 100644
index 00000000..1c94430d
--- /dev/null
+++ b/apps/docs/content/es/docs/01-app/02-guides/debugging.mdx
@@ -0,0 +1,181 @@
+---
+source-updated-at: 2025-05-16T04:52:11.000Z
+translation-updated-at: 2025-06-02T20:01:55.312Z
+title: Cómo utilizar herramientas de depuración con Next.js
+nav_title: Depuración
+description: Aprende a depurar tu aplicación Next.js con VS Code, Chrome DevTools o Firefox DevTools.
+---
+
+{/* 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. */}
+
+Esta documentación explica cómo puedes depurar el código frontend y backend de tu aplicación Next.js con soporte completo de mapas de fuentes utilizando el [depurador de VS Code](https://code.visualstudio.com/docs/editor/debugging), [Chrome DevTools](https://developers.google.com/web/tools/chrome-devtools) o [Firefox DevTools](https://firefox-source-docs.mozilla.org/devtools-user/).
+
+Cualquier depurador que pueda conectarse a Node.js también puede usarse para depurar una aplicación Next.js. Puedes encontrar más detalles en la [Guía de Depuración de Node.js](https://nodejs.org/en/docs/guides/debugging-getting-started/).
+
+## Depuración con VS Code
+
+Crea un archivo llamado `.vscode/launch.json` en la raíz de tu proyecto con el siguiente contenido:
+
+```json filename="launch.json"
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "Next.js: depuración del lado del servidor",
+ "type": "node-terminal",
+ "request": "launch",
+ "command": "npm run dev"
+ },
+ {
+ "name": "Next.js: depuración del lado del cliente",
+ "type": "chrome",
+ "request": "launch",
+ "url": "http://localhost:3000"
+ },
+ {
+ "name": "Next.js: depuración del lado del cliente (Firefox)",
+ "type": "firefox",
+ "request": "launch",
+ "url": "http://localhost:3000",
+ "reAttach": true,
+ "pathMappings": [
+ {
+ "url": "webpack://_N_E",
+ "path": "${workspaceFolder}"
+ }
+ ]
+ },
+ {
+ "name": "Next.js: depuración de pila completa",
+ "type": "node",
+ "request": "launch",
+ "program": "${workspaceFolder}/node_modules/next/dist/bin/next",
+ "runtimeArgs": ["--inspect"],
+ "skipFiles": ["/**"],
+ "serverReadyAction": {
+ "action": "debugWithEdge",
+ "killOnServerStop": true,
+ "pattern": "- Local:.+(https?://.+)",
+ "uriFormat": "%s",
+ "webRoot": "${workspaceFolder}"
+ }
+ }
+ ]
+}
+```
+
+> **Nota**: Para usar la depuración con Firefox en VS Code, necesitarás instalar la [extensión Firefox Debugger](https://marketplace.visualstudio.com/items?itemName=firefox-devtools.vscode-firefox-debug).
+
+`npm run dev` puede reemplazarse por `yarn dev` si estás usando Yarn o `pnpm dev` si estás usando pnpm.
+
+En la configuración "Next.js: depuración de pila completa", `serverReadyAction.action` especifica qué navegador abrir cuando el servidor esté listo. `debugWithEdge` significa lanzar el navegador Edge. Si estás usando Chrome, cambia este valor a `debugWithChrome`.
+
+Si estás [cambiando el número de puerto](/docs/pages/api-reference/cli/next#next-dev-options) en el que inicia tu aplicación, reemplaza el `3000` en `http://localhost:3000` con el puerto que estés usando.
+
+Si estás ejecutando Next.js desde un directorio que no es la raíz (por ejemplo, si estás usando Turborepo), entonces necesitas agregar `cwd` a las tareas de depuración del lado del servidor y de pila completa. Por ejemplo, `"cwd": "${workspaceFolder}/apps/web"`.
+
+Ahora ve al panel de Depuración (`Ctrl+Shift+D` en Windows/Linux, `⇧+⌘+D` en macOS), selecciona una configuración de lanzamiento, luego presiona `F5` o selecciona **Debug: Start Debugging** desde la Paleta de Comandos para iniciar tu sesión de depuración.
+
+## Usando el depurador en Jetbrains WebStorm
+
+Haz clic en el menú desplegable que lista la configuración de tiempo de ejecución y haz clic en `Edit Configurations...`. Crea una configuración de depuración `JavaScript Debug` con `http://localhost:3000` como la URL. Personalízala a tu gusto (por ejemplo, navegador para depuración, guardar como archivo de proyecto) y haz clic en `OK`. Ejecuta esta configuración de depuración y el navegador seleccionado debería abrirse automáticamente. En este punto, deberías tener 2 aplicaciones en modo de depuración: la aplicación Node NextJS y la aplicación cliente/navegador.
+
+## Depuración con DevTools del navegador
+
+### Código del lado del cliente
+
+Inicia tu servidor de desarrollo como de costumbre ejecutando `next dev`, `npm run dev` o `yarn dev`. Una vez que el servidor inicie, abre `http://localhost:3000` (o tu URL alternativa) en tu navegador preferido.
+
+Para Chrome:
+
+- Abre las Herramientas de Desarrollo de Chrome (`Ctrl+Shift+J` en Windows/Linux, `⌥+⌘+I` en macOS)
+- Ve a la pestaña **Sources**
+
+Para Firefox:
+
+- Abre las Herramientas de Desarrollo de Firefox (`Ctrl+Shift+I` en Windows/Linux, `⌥+⌘+I` en macOS)
+- Ve a la pestaña **Debugger**
+
+En cualquier navegador, cada vez que tu código del lado del cliente alcance una declaración [`debugger`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/debugger), la ejecución del código se pausará y ese archivo aparecerá en el área de depuración. También puedes buscar archivos para establecer puntos de interrupción manualmente:
+
+- En Chrome: Presiona `Ctrl+P` en Windows/Linux o `⌘+P` en macOS
+- En Firefox: Presiona `Ctrl+P` en Windows/Linux o `⌘+P` en macOS, o usa el árbol de archivos en el panel izquierdo
+
+Ten en cuenta que al buscar, tus archivos fuente tendrán rutas que comienzan con `webpack://_N_E/./`.
+
+### Código del lado del servidor
+
+Para depurar código del lado del servidor de Next.js con DevTools del navegador, necesitas pasar la bandera [`--inspect`](https://nodejs.org/api/cli.html#cli_inspect_host_port) al proceso subyacente de Node.js:
+
+```bash filename="Terminal"
+NODE_OPTIONS='--inspect' next dev
+```
+
+> **Bueno saber**: Usa `NODE_OPTIONS='--inspect=0.0.0.0'` para permitir acceso remoto de depuración fuera de localhost, como cuando ejecutas la aplicación en un contenedor Docker.
+
+Si estás usando `npm run dev` o `yarn dev`, entonces deberías actualizar el script `dev` en tu `package.json`:
+
+```json filename="package.json"
+{
+ "scripts": {
+ "dev": "NODE_OPTIONS='--inspect' next dev"
+ }
+}
+```
+
+Iniciar el servidor de desarrollo de Next.js con la bandera `--inspect` se verá algo así:
+
+```bash filename="Terminal"
+Debugger listening on ws://127.0.0.1:9229/0cf90313-350d-4466-a748-cd60f4e47c95
+For help, see: https://nodejs.org/en/docs/inspector
+ready - started server on 0.0.0.0:3000, url: http://localhost:3000
+```
+
+Para Chrome:
+
+1. Abre una nueva pestaña y visita `chrome://inspect`
+2. Haz clic en **Configure...** para asegurarte de que ambos puertos de depuración estén listados
+3. Agrega tanto `localhost:9229` como `localhost:9230` si no están presentes
+4. Busca tu aplicación Next.js en la sección **Remote Target**
+5. Haz clic en **inspect** para abrir una ventana separada de DevTools
+6. Ve a la pestaña **Sources**
+
+Para Firefox:
+
+1. Abre una nueva pestaña y visita `about:debugging`
+2. Haz clic en **This Firefox** en la barra lateral izquierda
+3. En **Remote Targets**, encuentra tu aplicación Next.js
+4. Haz clic en **Inspect** para abrir el depurador
+5. Ve a la pestaña **Debugger**
+
+Depurar código del lado del servidor funciona de manera similar a la depuración del lado del cliente. Al buscar archivos (`Ctrl+P`/`⌘+P`), tus archivos fuente tendrán rutas que comienzan con `webpack://{nombre-de-la-aplicación}/./` (donde `{nombre-de-la-aplicación}` será reemplazado con el nombre de tu aplicación según tu archivo `package.json`).
+
+### Inspeccionar errores del servidor con DevTools del navegador
+
+Cuando encuentres un error, inspeccionar el código fuente puede ayudar a rastrear la causa raíz de los errores.
+
+Next.js mostrará un ícono de Node.js debajo del indicador de versión de Next.js en el overlay de errores. Al hacer clic en ese ícono, la URL de DevTools se copiará a tu portapapeles. Puedes abrir una nueva pestaña del navegador con esa URL para inspeccionar el proceso del servidor Next.js.
+
+### Depuración en Windows
+
+Los usuarios de Windows pueden encontrar un problema al usar `NODE_OPTIONS='--inspect'` ya que esa sintaxis no es compatible con plataformas Windows. Para solucionar esto, instala el paquete [`cross-env`](https://www.npmjs.com/package/cross-env) como dependencia de desarrollo (`-D` con `npm` y `yarn`) y reemplaza el script `dev` con lo siguiente.
+
+```json filename="package.json"
+{
+ "scripts": {
+ "dev": "cross-env NODE_OPTIONS='--inspect' next dev"
+ }
+}
+```
+
+`cross-env` establecerá la variable de entorno `NODE_OPTIONS` independientemente de la plataforma en la que estés (incluyendo Mac, Linux y Windows) y te permitirá depurar consistentemente entre dispositivos y sistemas operativos.
+
+> **Bueno saber**: Asegúrate de que Windows Defender esté desactivado en tu máquina. Este servicio externo verificará _cada lectura de archivo_, lo que según reportes aumenta considerablemente el tiempo de Fast Refresh con `next dev`. Este es un problema conocido, no relacionado con Next.js, pero sí afecta el desarrollo con Next.js.
+
+## Más información
+
+Para aprender más sobre cómo usar un depurador de JavaScript, consulta la siguiente documentación:
+
+- [Depuración de Node.js en VS Code: Puntos de interrupción](https://code.visualstudio.com/docs/nodejs/nodejs-debugging#_breakpoints)
+- [Chrome DevTools: Depurar JavaScript](https://developers.google.com/web/tools/chrome-devtools/javascript)
+- [Firefox DevTools: Depurador](https://firefox-source-docs.mozilla.org/devtools-user/debugger/)
\ No newline at end of file
diff --git a/apps/docs/content/es/docs/01-app/02-guides/draft-mode.mdx b/apps/docs/content/es/docs/01-app/02-guides/draft-mode.mdx
new file mode 100644
index 00000000..75d1e665
--- /dev/null
+++ b/apps/docs/content/es/docs/01-app/02-guides/draft-mode.mdx
@@ -0,0 +1,217 @@
+---
+source-updated-at: 2025-05-19T22:31:51.000Z
+translation-updated-at: 2025-06-02T20:01:29.090Z
+title: Cómo previsualizar contenido con el Modo Borrador en Next.js
+nav_title: Modo Borrador
+description: Next.js incluye el modo borrador para alternar entre páginas estáticas y dinámicas. Aquí puedes aprender cómo funciona con el App Router.
+related:
+ title: Próximos pasos
+ description: Consulta la referencia de API para más información sobre cómo usar el Modo Borrador.
+ links:
+ - app/api-reference/functions/draft-mode
+---
+
+El **Modo Borrador** te permite previsualizar contenido en borrador desde tu CMS headless en tu aplicación Next.js. Esto es útil para páginas estáticas generadas en tiempo de compilación, ya que permite cambiar a [renderizado dinámico](/docs/app/getting-started/partial-prerendering#dynamic-rendering) y ver los cambios en borrador sin necesidad de reconstruir todo el sitio.
+
+Esta guía explica cómo habilitar y usar el Modo Borrador.
+
+## Paso 1: Crear un Manejador de Ruta
+
+Crea un [Manejador de Ruta](/docs/app/building-your-application/routing/route-handlers). Puede tener cualquier nombre, por ejemplo, `app/api/draft/route.ts`.
+
+```ts filename="app/api/draft/route.ts" switcher
+export async function GET(request: Request) {
+ return new Response('')
+}
+```
+
+```js filename="app/api/draft/route.js" switcher
+export async function GET() {
+ return new Response('')
+}
+```
+
+Luego, importa la función [`draftMode`](/docs/app/api-reference/functions/draft-mode) y llama al método `enable()`.
+
+```ts filename="app/api/draft/route.ts" switcher
+import { draftMode } from 'next/headers'
+
+export async function GET(request: Request) {
+ const draft = await draftMode()
+ draft.enable()
+ return new Response('Draft mode is enabled')
+}
+```
+
+```js filename="app/api/draft/route.js" switcher
+import { draftMode } from 'next/headers'
+
+export async function GET(request) {
+ const draft = await draftMode()
+ draft.enable()
+ return new Response('Draft mode is enabled')
+}
+```
+
+Esto establecerá una **cookie** para habilitar el modo borrador. Las solicitudes posteriores que incluyan esta cookie activarán el modo borrador y cambiarán el comportamiento de las páginas generadas estáticamente.
+
+Puedes probar esto manualmente visitando `/api/draft` y revisando las herramientas de desarrollo de tu navegador. Observa el encabezado de respuesta `Set-Cookie` con una cookie llamada `__prerender_bypass`.
+
+## Paso 2: Acceder al Manejador de Ruta desde tu CMS Headless
+
+> Estos pasos asumen que tu CMS headless admite la configuración de **URLs de borrador personalizadas**. Si no es así, aún puedes usar este método para proteger tus URLs de borrador, pero necesitarás construir y acceder a la URL manualmente. Los pasos específicos variarán según el CMS headless que uses.
+
+Para acceder de forma segura al Manejador de Ruta desde tu CMS headless:
+
+1. Crea un **token secreto** usando un generador de tokens de tu elección. Este secreto solo lo conocerán tu aplicación Next.js y tu CMS headless.
+2. Si tu CMS headless admite URLs de borrador personalizadas, especifica una URL de borrador (esto asume que tu Manejador de Ruta está en `app/api/draft/route.ts`). Por ejemplo:
+
+```bash filename="Terminal"
+https:///api/draft?secret=&slug=
+```
+
+> - `` debe ser tu dominio de despliegue.
+> - `` debe reemplazarse con el token secreto que generaste.
+> - `` debe ser la ruta de la página que deseas ver. Si quieres ver `/posts/uno`, entonces debes usar `&slug=/posts/uno`.
+>
+> Tu CMS headless podría permitirte incluir una variable en la URL de borrador para que `` se establezca dinámicamente según los datos del CMS, como: `&slug=/posts/{entry.fields.slug}`
+
+3. En tu Manejador de Ruta, verifica que el secreto coincida y que exista el parámetro `slug` (si no, la solicitud debe fallar), llama a `draftMode.enable()` para establecer la cookie. Luego, redirige el navegador a la ruta especificada por `slug`:
+
+```ts filename="app/api/draft/route.ts" switcher
+import { draftMode } from 'next/headers'
+import { redirect } from 'next/navigation'
+
+export async function GET(request: Request) {
+ // Analiza los parámetros de la cadena de consulta
+ const { searchParams } = new URL(request.url)
+ const secret = searchParams.get('secret')
+ const slug = searchParams.get('slug')
+
+ // Verifica el secreto y los parámetros
+ // Este secreto solo debe conocerse en este Manejador de Ruta y el CMS
+ if (secret !== 'MY_SECRET_TOKEN' || !slug) {
+ return new Response('Token inválido', { status: 401 })
+ }
+
+ // Consulta el CMS headless para verificar si existe el `slug` proporcionado
+ // getPostBySlug implementaría la lógica necesaria para consultar el CMS headless
+ const post = await getPostBySlug(slug)
+
+ // Si el slug no existe, evita que se habilite el modo borrador
+ if (!post) {
+ return new Response('Slug inválido', { status: 401 })
+ }
+
+ // Habilita el Modo Borrador estableciendo la cookie
+ const draft = await draftMode()
+ draft.enable()
+
+ // Redirige a la ruta del post obtenido
+ // No redirigimos a searchParams.slug para evitar vulnerabilidades de redirección abierta
+ redirect(post.slug)
+}
+```
+
+```js filename="app/api/draft/route.js" switcher
+import { draftMode } from 'next/headers'
+import { redirect } from 'next/navigation'
+
+export async function GET(request) {
+ // Analiza los parámetros de la cadena de consulta
+ const { searchParams } = new URL(request.url)
+ const secret = searchParams.get('secret')
+ const slug = searchParams.get('slug')
+
+ // Verifica el secreto y los parámetros
+ // Este secreto solo debe conocerse en este Manejador de Ruta y el CMS
+ if (secret !== 'MY_SECRET_TOKEN' || !slug) {
+ return new Response('Token inválido', { status: 401 })
+ }
+
+ // Consulta el CMS headless para verificar si existe el `slug` proporcionado
+ // getPostBySlug implementaría la lógica necesaria para consultar el CMS headless
+ const post = await getPostBySlug(slug)
+
+ // Si el slug no existe, evita que se habilite el modo borrador
+ if (!post) {
+ return new Response('Slug inválido', { status: 401 })
+ }
+
+ // Habilita el Modo Borrador estableciendo la cookie
+ const draft = await draftMode()
+ draft.enable()
+
+ // Redirige a la ruta del post obtenido
+ // No redirigimos a searchParams.slug para evitar vulnerabilidades de redirección abierta
+ redirect(post.slug)
+}
+```
+
+Si tiene éxito, el navegador será redirigido a la ruta que deseas ver con la cookie del modo borrador.
+
+## Paso 3: Previsualizar el Contenido en Borrador
+
+El siguiente paso es actualizar tu página para verificar el valor de `draftMode().isEnabled`.
+
+Si solicitas una página que tiene la cookie establecida, los datos se obtendrán en **tiempo de solicitud** (en lugar de en tiempo de compilación).
+
+Además, el valor de `isEnabled` será `true`.
+
+```tsx filename="app/page.tsx" switcher
+// página que obtiene datos
+import { draftMode } from 'next/headers'
+
+async function getData() {
+ const { isEnabled } = await draftMode()
+
+ const url = isEnabled
+ ? 'https://draft.example.com'
+ : 'https://production.example.com'
+
+ const res = await fetch(url)
+
+ return res.json()
+}
+
+export default async function Page() {
+ const { title, desc } = await getData()
+
+ return (
+
+ {title}
+ {desc}
+
+ )
+}
+```
+
+```jsx filename="app/page.js" switcher
+// página que obtiene datos
+import { draftMode } from 'next/headers'
+
+async function getData() {
+ const { isEnabled } = await draftMode()
+
+ const url = isEnabled
+ ? 'https://draft.example.com'
+ : 'https://production.example.com'
+
+ const res = await fetch(url)
+
+ return res.json()
+}
+
+export default async function Page() {
+ const { title, desc } = await getData()
+
+ return (
+
+ {title}
+ {desc}
+
+ )
+}
+```
+
+Si accedes al Manejador de Ruta de borrador (con `secret` y `slug`) desde tu CMS headless o manualmente usando la URL, ahora deberías poder ver el contenido en borrador. Y, si actualizas tu borrador sin publicar, deberías poder ver los cambios.
\ No newline at end of file
diff --git a/apps/docs/content/es/docs/01-app/02-guides/environment-variables.mdx b/apps/docs/content/es/docs/01-app/02-guides/environment-variables.mdx
new file mode 100644
index 00000000..c76ac656
--- /dev/null
+++ b/apps/docs/content/es/docs/01-app/02-guides/environment-variables.mdx
@@ -0,0 +1,280 @@
+---
+source-updated-at: 2025-05-16T04:52:11.000Z
+translation-updated-at: 2025-06-02T20:02:07.296Z
+title: Cómo usar variables de entorno en Next.js
+nav_title: Variables de Entorno
+description: Aprende a añadir y acceder a variables de entorno en tu aplicación Next.js.
+---
+
+{/* El contenido de este documento es compartido entre el enrutador de app y pages. Puedes usar el componente `Contenido ` para añadir contenido específico del Enrutador de Pages. Cualquier contenido compartido no debe estar envuelto en un componente. */}
+
+Next.js incluye soporte integrado para variables de entorno, lo que te permite hacer lo siguiente:
+
+- [Usar `.env` para cargar variables de entorno](#cargar-variables-de-entorno)
+- [Incluir variables de entorno para el navegador prefijándolas con `NEXT_PUBLIC_`](#incluir-variables-de-entorno-para-el-navegador)
+
+> **Advertencia:** La plantilla predeterminada de `create-next-app` asegura que todos los archivos `.env` se añadan a tu `.gitignore`. Casi nunca querrás subir estos archivos a tu repositorio.
+
+## Cargar Variables de Entorno
+
+Next.js tiene soporte integrado para cargar variables de entorno desde archivos `.env*` hacia `process.env`.
+
+```txt filename=".env"
+DB_HOST=localhost
+DB_USER=myuser
+DB_PASS=mypassword
+```
+
+
+
+Esto carga `process.env.DB_HOST`, `process.env.DB_USER` y `process.env.DB_PASS` en el entorno de Node.js automáticamente, permitiéndote usarlas en [métodos de obtención de datos de Next.js](/docs/pages/building-your-application/data-fetching) y [rutas API](/docs/pages/building-your-application/routing/api-routes).
+
+Por ejemplo, usando [`getStaticProps`](/docs/pages/building-your-application/data-fetching/get-static-props):
+
+```js filename="pages/index.js"
+export async function getStaticProps() {
+ const db = await myDB.connect({
+ host: process.env.DB_HOST,
+ username: process.env.DB_USER,
+ password: process.env.DB_PASS,
+ })
+ // ...
+}
+```
+
+
+
+
+
+> **Nota**: Next.js también soporta variables multilínea dentro de tus archivos `.env*`:
+>
+> ```bash
+> # .env
+>
+> # puedes escribir con saltos de línea
+> PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
+> ...
+> Kh9NV...
+> ...
+> -----END DSA PRIVATE KEY-----"
+>
+> # o con `\n` dentro de comillas dobles
+> PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\nKh9NV...\n-----END DSA PRIVATE KEY-----\n"
+> ```
+
+> **Nota**: Si estás usando una carpeta `/src`, ten en cuenta que Next.js cargará los archivos .env **solo** desde la carpeta principal y **no** desde la carpeta `/src`.
+> Esto carga `process.env.DB_HOST`, `process.env.DB_USER` y `process.env.DB_PASS` en el entorno de Node.js automáticamente, permitiéndote usarlas en [Route Handlers](/docs/app/building-your-application/routing/route-handlers).
+
+Por ejemplo:
+
+```js filename="app/api/route.js"
+export async function GET() {
+ const db = await myDB.connect({
+ host: process.env.DB_HOST,
+ username: process.env.DB_USER,
+ password: process.env.DB_PASS,
+ })
+ // ...
+}
+```
+
+
+
+### Cargar Variables de Entorno con `@next/env`
+
+Si necesitas cargar variables de entorno fuera del entorno de ejecución de Next.js, como en un archivo de configuración raíz para un ORM o un ejecutor de pruebas, puedes usar el paquete `@next/env`.
+
+Este paquete se usa internamente por Next.js para cargar variables de entorno desde archivos `.env*`.
+
+Para usarlo, instala el paquete y utiliza la función `loadEnvConfig` para cargar las variables de entorno:
+
+```bash
+npm install @next/env
+```
+
+```tsx filename="envConfig.ts" switcher
+import { loadEnvConfig } from '@next/env'
+
+const projectDir = process.cwd()
+loadEnvConfig(projectDir)
+```
+
+```jsx filename="envConfig.js" switcher
+import { loadEnvConfig } from '@next/env'
+
+const projectDir = process.cwd()
+loadEnvConfig(projectDir)
+```
+
+Luego, puedes importar la configuración donde sea necesario. Por ejemplo:
+
+```tsx filename="orm.config.ts" switcher
+import './envConfig.ts'
+
+export default defineConfig({
+ dbCredentials: {
+ connectionString: process.env.DATABASE_URL!,
+ },
+})
+```
+
+```jsx filename="orm.config.js" switcher
+import './envConfig.js'
+
+export default defineConfig({
+ dbCredentials: {
+ connectionString: process.env.DATABASE_URL,
+ },
+})
+```
+
+### Referenciar Otras Variables
+
+Next.js expandirá automáticamente variables que usen `$` para referenciar otras variables, ej. `$VARIABLE` dentro de tus archivos `.env*`. Esto te permite referenciar otros secretos. Por ejemplo:
+
+```txt filename=".env"
+TWITTER_USER=nextjs
+TWITTER_URL=https://x.com/$TWITTER_USER
+```
+
+En el ejemplo anterior, `process.env.TWITTER_URL` se establecería como `https://x.com/nextjs`.
+
+> **Bueno saber**: Si necesitas usar una variable con un `$` en el valor real, debe escaparse, ej. `\$`.
+
+## Incluir Variables de Entorno para el Navegador
+
+Las variables de entorno que no tengan el prefijo `NEXT_PUBLIC_` solo están disponibles en el entorno de Node.js, lo que significa que no son accesibles para el navegador (el cliente se ejecuta en un _entorno_ diferente).
+
+Para hacer que el valor de una variable de entorno sea accesible en el navegador, Next.js puede "insertar" un valor, en tiempo de compilación, en el paquete js que se entrega al cliente, reemplazando todas las referencias a `process.env.[variable]` con un valor codificado. Para indicarle que haga esto, solo tienes que prefijar la variable con `NEXT_PUBLIC_`. Por ejemplo:
+
+```txt filename="Terminal"
+NEXT_PUBLIC_ANALYTICS_ID=abcdefghijk
+```
+
+Esto le dirá a Next.js que reemplace todas las referencias a `process.env.NEXT_PUBLIC_ANALYTICS_ID` en el entorno de Node.js con el valor del entorno en el que ejecutes `next build`, permitiéndote usarlo en cualquier parte de tu código. Se insertará en cualquier JavaScript enviado al navegador.
+
+> **Nota**: Después de compilar, tu aplicación ya no responderá a cambios en estas variables de entorno. Por ejemplo, si usas un pipeline de Heroku para promocionar slugs compilados en un entorno a otro, o si compilas y despliegas una única imagen Docker a múltiples entornos, todas las variables `NEXT_PUBLIC_` se congelarán con el valor evaluado en tiempo de compilación, por lo que estos valores deben establecerse apropiadamente cuando el proyecto se compile. Si necesitas acceder a valores de entorno en tiempo de ejecución, tendrás que configurar tu propia API para proporcionarlos al cliente (ya sea bajo demanda o durante la inicialización).
+
+```js filename="pages/index.js"
+import setupAnalyticsService from '../lib/my-analytics-service'
+
+// 'NEXT_PUBLIC_ANALYTICS_ID' puede usarse aquí ya que tiene el prefijo 'NEXT_PUBLIC_'.
+// Se transformará en tiempo de compilación a `setupAnalyticsService('abcdefghijk')`.
+setupAnalyticsService(process.env.NEXT_PUBLIC_ANALYTICS_ID)
+
+function HomePage() {
+ return Hello World
+}
+
+export default HomePage
+```
+
+Ten en cuenta que las búsquedas dinámicas _no_ se insertarán, como:
+
+```js
+// Esto NO se insertará, porque usa una variable
+const varName = 'NEXT_PUBLIC_ANALYTICS_ID'
+setupAnalyticsService(process.env[varName])
+
+// Esto NO se insertará, porque usa una variable
+const env = process.env
+setupAnalyticsService(env.NEXT_PUBLIC_ANALYTICS_ID)
+```
+
+### Variables de Entorno en Tiempo de Ejecución
+
+Next.js puede soportar tanto variables de entorno en tiempo de compilación como en tiempo de ejecución.
+
+**Por defecto, las variables de entorno solo están disponibles en el servidor**. Para exponer una variable de entorno al navegador, debe tener el prefijo `NEXT_PUBLIC_`. Sin embargo, estas variables públicas se insertarán en el paquete JavaScript durante `next build`.
+
+
+
+Para leer variables de entorno en tiempo de ejecución, recomendamos usar `getServerSideProps` o [adoptar incrementalmente el App Router](/docs/app/guides/migrating/app-router-migration).
+
+
+
+
+
+Puedes leer de forma segura variables de entorno en el servidor durante el renderizado dinámico:
+
+```tsx filename="app/page.ts" switcher
+import { connection } from 'next/server'
+
+export default async function Component() {
+ await connection()
+ // cookies, headers y otras APIs Dinámicas
+ // también optarán por el renderizado dinámico, lo que significa
+ // que esta variable de entorno se evalúa en tiempo de ejecución
+ const value = process.env.MY_VALUE
+ // ...
+}
+```
+
+```jsx filename="app/page.js" switcher
+import { connection } from 'next/server'
+
+export default async function Component() {
+ await connection()
+ // cookies, headers y otras APIs Dinámicas
+ // también optarán por el renderizado dinámico, lo que significa
+ // que esta variable de entorno se evalúa en tiempo de ejecución
+ const value = process.env.MY_VALUE
+ // ...
+}
+```
+
+
+
+Esto te permite usar una única imagen Docker que puede promocionarse a través de múltiples entornos con diferentes valores.
+
+**Bueno saber:**
+
+- Puedes ejecutar código al iniciar el servidor usando la [función `register`](/docs/app/guides/instrumentation).
+- No recomendamos usar la opción [`runtimeConfig`](/docs/pages/api-reference/config/next-config-js/runtime-configuration), ya que no funciona con el modo de salida standalone. En su lugar, recomendamos [adoptar incrementalmente](/docs/app/guides/migrating/app-router-migration) el App Router si necesitas esta característica.
+
+## Variables de Entorno de Prueba
+
+Además de los entornos `development` y `production`, hay una tercera opción disponible: `test`. De la misma manera que puedes establecer valores predeterminados para entornos de desarrollo o producción, puedes hacer lo mismo con un archivo `.env.test` para el entorno de `testing` (aunque este no es tan común como los dos anteriores). Next.js no cargará variables de entorno desde `.env.development` o `.env.production` en el entorno de `testing`.
+
+Esto es útil cuando ejecutas pruebas con herramientas como `jest` o `cypress` donde necesitas establecer variables de entorno específicas solo para propósitos de prueba. Los valores predeterminados de prueba se cargarán si `NODE_ENV` está establecido como `test`, aunque normalmente no necesitas hacer esto manualmente ya que las herramientas de prueba lo manejarán por ti.
+
+Hay una pequeña diferencia entre el entorno `test`, y los entornos `development` y `production` que debes tener en cuenta: `.env.local` no se cargará, ya que esperas que las pruebas produzcan los mismos resultados para todos. De esta manera, cada ejecución de prueba usará los mismos valores predeterminados de entorno en diferentes ejecuciones al ignorar tu `.env.local` (que está destinado a sobrescribir el conjunto predeterminado).
+
+> **Bueno saber**: similar a las Variables de Entorno Predeterminadas, el archivo `.env.test` debe incluirse en tu repositorio, pero `.env.test.local` no, ya que `.env*.local` están destinados a ignorarse a través de `.gitignore`.
+
+Mientras ejecutas pruebas unitarias, puedes asegurarte de cargar tus variables de entorno de la misma manera que lo hace Next.js utilizando la función `loadEnvConfig` del paquete `@next/env`.
+
+```js
+// Lo siguiente puede usarse en un archivo de configuración global de Jest o similar para tu configuración de pruebas
+import { loadEnvConfig } from '@next/env'
+
+export default async () => {
+ const projectDir = process.cwd()
+ loadEnvConfig(projectDir)
+}
+```
+
+## Orden de Carga de Variables de Entorno
+
+Las variables de entorno se buscan en los siguientes lugares, en orden, deteniéndose una vez que se encuentra la variable.
+
+1. `process.env`
+1. `.env.$(NODE_ENV).local`
+1. `.env.local` (No se verifica cuando `NODE_ENV` es `test`.)
+1. `.env.$(NODE_ENV)`
+1. `.env`
+
+Por ejemplo, si `NODE_ENV` es `development` y defines una variable tanto en `.env.development.local` como en `.env`, se usará el valor en `.env.development.local`.
+
+> **Bueno saber**: Los valores permitidos para `NODE_ENV` son `production`, `development` y `test`.
+
+## Bueno Saber
+
+- Si estás usando una [carpeta `/src`](/docs/app/api-reference/file-conventions/src-folder), los archivos `.env.*` deben permanecer en la raíz de tu proyecto.
+- Si la variable de entorno `NODE_ENV` no está asignada, Next.js automáticamente asigna `development` al ejecutar el comando `next dev`, o `production` para todos los demás comandos.
+
+## Historial de Versiones
+
+| Versión | Cambios |
+| -------- | --------------------------------------------- |
+| `v9.4.0` | Soporte para `.env` y `NEXT_PUBLIC_` añadido. |
\ No newline at end of file
diff --git a/apps/docs/content/es/docs/01-app/02-guides/forms.mdx b/apps/docs/content/es/docs/01-app/02-guides/forms.mdx
new file mode 100644
index 00000000..b8db0e3b
--- /dev/null
+++ b/apps/docs/content/es/docs/01-app/02-guides/forms.mdx
@@ -0,0 +1,493 @@
+---
+source-updated-at: 2025-06-01T01:32:20.000Z
+translation-updated-at: 2025-06-02T20:02:19.162Z
+title: Cómo crear formularios con Acciones de Servidor (Server Actions)
+nav_title: Formularios
+description: Aprenda a crear formularios en Next.js utilizando Acciones de Servidor (Server Actions) de React.
+---
+
+Las Acciones de Servidor (Server Actions) de React son [Funciones del Servidor](https://react.dev/reference/rsc/server-functions) que se ejecutan en el servidor. Pueden ser llamadas en Componentes del Servidor y del Cliente para manejar envíos de formularios. Esta guía le mostrará cómo crear formularios en Next.js con Acciones de Servidor.
+
+## Cómo funciona
+
+React extiende el elemento HTML [``](https://developer.mozilla.org/docs/Web/HTML/Element/form) para permitir invocar Acciones de Servidor mediante el atributo [`action`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/form#action).
+
+Cuando se usa en un formulario, la función recibe automáticamente el objeto [`FormData`](https://developer.mozilla.org/docs/Web/API/FormData/FormData). Luego puede extraer los datos utilizando los [métodos nativos de FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData#instance_methods):
+
+```tsx filename="app/invoices/page.tsx" switcher
+export default function Page() {
+ async function createInvoice(formData: FormData) {
+ 'use server'
+
+ const rawFormData = {
+ customerId: formData.get('customerId'),
+ amount: formData.get('amount'),
+ status: formData.get('status'),
+ }
+
+ // mutate data
+ // revalidate the cache
+ }
+
+ return ...
+}
+```
+
+```jsx filename="app/invoices/page.js" switcher
+export default function Page() {
+ async function createInvoice(formData) {
+ 'use server'
+
+ const rawFormData = {
+ customerId: formData.get('customerId'),
+ amount: formData.get('amount'),
+ status: formData.get('status'),
+ }
+
+ // mutate data
+ // revalidate the cache
+ }
+
+ return ...
+}
+```
+
+> **Nota importante:** Cuando trabaje con formularios que tienen múltiples campos, puede usar el método [`entries()`](https://developer.mozilla.org/en-US/docs/Web/API/FormData/entries) con [`Object.fromEntries()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/fromEntries) de JavaScript. Por ejemplo: `const rawFormData = Object.fromEntries(formData)`.
+
+## Pasar argumentos adicionales
+
+Fuera de los campos del formulario, puede pasar argumentos adicionales a una Función del Servidor usando el método [`bind`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind) de JavaScript. Por ejemplo, para pasar el argumento `userId` a la Función del Servidor `updateUser`:
+
+```tsx filename="app/client-component.tsx" highlight={6} switcher
+'use client'
+
+import { updateUser } from './actions'
+
+export function UserProfile({ userId }: { userId: string }) {
+ const updateUserWithId = updateUser.bind(null, userId)
+
+ return (
+
+
+ Actualizar nombre de usuario
+
+ )
+}
+```
+
+```jsx filename="app/client-component.js" highlight={6} switcher
+'use client'
+
+import { updateUser } from './actions'
+
+export function UserProfile({ userId }) {
+ const updateUserWithId = updateUser.bind(null, userId)
+
+ return (
+
+
+ Actualizar nombre de usuario
+
+ )
+}
+```
+
+La Función del Servidor recibirá el `userId` como argumento adicional:
+
+```ts filename="app/actions.ts" switcher
+'use server'
+
+export async function updateUser(userId: string, formData: FormData) {}
+```
+
+```js filename="app/actions.js" switcher
+'use server'
+
+export async function updateUser(userId, formData) {}
+```
+
+> **Nota importante**:
+>
+> - Una alternativa es pasar los argumentos como campos ocultos en el formulario (ej. ` `). Sin embargo, el valor será parte del HTML renderizado y no estará codificado.
+> - `bind` funciona tanto en Componentes del Servidor como del Cliente y soporta mejora progresiva.
+
+## Validación de formularios
+
+Los formularios pueden validarse en el cliente o en el servidor.
+
+- Para **validación del lado del cliente**, puede usar atributos HTML como `required` y `type="email"` para validación básica.
+- Para **validación del lado del servidor**, puede usar una biblioteca como [zod](https://zod.dev/) para validar los campos del formulario. Por ejemplo:
+
+```tsx filename="app/actions.ts" switcher
+'use server'
+
+import { z } from 'zod'
+
+const schema = z.object({
+ email: z.string({
+ invalid_type_error: 'Correo electrónico inválido',
+ }),
+})
+
+export default async function createUser(formData: FormData) {
+ const validatedFields = schema.safeParse({
+ email: formData.get('email'),
+ })
+
+ // Retornar temprano si los datos del formulario son inválidos
+ if (!validatedFields.success) {
+ return {
+ errors: validatedFields.error.flatten().fieldErrors,
+ }
+ }
+
+ // Mutar datos
+}
+```
+
+```jsx filename="app/actions.js" switcher
+'use server'
+
+import { z } from 'zod'
+
+const schema = z.object({
+ email: z.string({
+ invalid_type_error: 'Correo electrónico inválido',
+ }),
+})
+
+export default async function createsUser(formData) {
+ const validatedFields = schema.safeParse({
+ email: formData.get('email'),
+ })
+
+ // Retornar temprano si los datos del formulario son inválidos
+ if (!validatedFields.success) {
+ return {
+ errors: validatedFields.error.flatten().fieldErrors,
+ }
+ }
+
+ // Mutar datos
+}
+```
+
+## Errores de validación
+
+Para mostrar errores o mensajes de validación, convierta el componente que define el `` en un Componente del Cliente y use el hook [`useActionState`](https://react.dev/reference/react/useActionState) de React.
+
+Al usar `useActionState`, la firma de la función del Servidor cambiará para recibir un nuevo parámetro `prevState` o `initialState` como primer argumento.
+
+```tsx filename="app/actions.ts" highlight={4} switcher
+'use server'
+
+import { z } from 'zod'
+
+export async function createUser(initialState: any, formData: FormData) {
+ const validatedFields = schema.safeParse({
+ email: formData.get('email'),
+ })
+ // ...
+}
+```
+
+```jsx filename="app/actions.ts" highlight={4} switcher
+'use server'
+
+import { z } from 'zod'
+
+// ...
+
+export async function createUser(initialState, formData) {
+ const validatedFields = schema.safeParse({
+ email: formData.get('email'),
+ })
+ // ...
+}
+```
+
+Luego puede renderizar condicionalmente el mensaje de error basado en el objeto `state`.
+
+```tsx filename="app/ui/signup.tsx" highlight={11,18-20} switcher
+'use client'
+
+import { useActionState } from 'react'
+import { createUser } from '@/app/actions'
+
+const initialState = {
+ message: '',
+}
+
+export function Signup() {
+ const [state, formAction, pending] = useActionState(createUser, initialState)
+
+ return (
+
+ Correo electrónico
+
+ {/* ... */}
+ {state?.message}
+ Registrarse
+
+ )
+}
+```
+
+```jsx filename="app/ui/signup.js" highlight={11,18-20} switcher
+'use client'
+
+import { useActionState } from 'react'
+import { createUser } from '@/app/actions'
+
+const initialState = {
+ message: '',
+}
+
+export function Signup() {
+ const [state, formAction, pending] = useActionState(createUser, initialState)
+
+ return (
+
+ Correo electrónico
+
+ {/* ... */}
+ {state?.message}
+ Registrarse
+
+ )
+}
+```
+
+## Estados pendientes
+
+El hook [`useActionState`](https://react.dev/reference/react/useActionState) expone un booleano `pending` que puede usarse para mostrar un indicador de carga o deshabilitar el botón de envío mientras se ejecuta la acción.
+
+```tsx filename="app/ui/signup.tsx" highlight={7,12} switcher
+'use client'
+
+import { useActionState } from 'react'
+import { createUser } from '@/app/actions'
+
+export function Signup() {
+ const [state, formAction, pending] = useActionState(createUser, initialState)
+
+ return (
+
+ {/* Otros elementos del formulario */}
+ Registrarse
+
+ )
+}
+```
+
+```jsx filename="app/ui/signup.js" highlight={7,12} switcher
+'use client'
+
+import { useActionState } from 'react'
+import { createUser } from '@/app/actions'
+
+export function Signup() {
+ const [state, formAction, pending] = useActionState(createUser, initialState)
+
+ return (
+
+ {/* Otros elementos del formulario */}
+ Registrarse
+
+ )
+}
+```
+
+Alternativamente, puede usar el hook [`useFormStatus`](https://react.dev/reference/react-dom/hooks/useFormStatus) para mostrar un indicador de carga mientras se ejecuta la acción. Al usar este hook, necesitará crear un componente separado para renderizar el indicador de carga. Por ejemplo, para deshabilitar el botón cuando la acción está pendiente:
+
+```tsx filename="app/ui/button.tsx" highlight={6} switcher
+'use client'
+
+import { useFormStatus } from 'react-dom'
+
+export function SubmitButton() {
+ const { pending } = useFormStatus()
+
+ return (
+
+ Registrarse
+
+ )
+}
+```
+
+```jsx filename="app/ui/button.js" highlight={6} switcher
+'use client'
+
+import { useFormStatus } from 'react-dom'
+
+export function SubmitButton() {
+ const { pending } = useFormStatus()
+
+ return (
+
+ Registrarse
+
+ )
+}
+```
+
+Luego puede anidar el componente `SubmitButton` dentro del formulario:
+
+```tsx filename="app/ui/signup.tsx" switcher
+import { SubmitButton } from './button'
+import { createUser } from '@/app/actions'
+
+export function Signup() {
+ return (
+
+ {/* Otros elementos del formulario */}
+
+
+ )
+}
+```
+
+```jsx filename="app/ui/signup.js" switcher
+import { SubmitButton } from './button'
+import { createUser } from '@/app/actions'
+
+export function Signup() {
+ return (
+
+ {/* Otros elementos del formulario */}
+
+
+ )
+}
+```
+
+> **Nota importante:** En React 19, `useFormStatus` incluye claves adicionales en el objeto retornado, como data, method y action. Si no está usando React 19, solo está disponible la clave `pending`.
+
+## Actualizaciones optimistas
+
+Puede usar el hook [`useOptimistic`](https://react.dev/reference/react/useOptimistic) de React para actualizar optimistamente la UI antes de que la Función del Servidor termine de ejecutarse, en lugar de esperar la respuesta:
+
+```tsx filename="app/page.tsx" switcher
+'use client'
+
+import { useOptimistic } from 'react'
+import { send } from './actions'
+
+type Message = {
+ message: string
+}
+
+export function Thread({ messages }: { messages: Message[] }) {
+ const [optimisticMessages, addOptimisticMessage] = useOptimistic<
+ Message[],
+ string
+ >(messages, (state, newMessage) => [...state, { message: newMessage }])
+
+ const formAction = async (formData: FormData) => {
+ const message = formData.get('message') as string
+ addOptimisticMessage(message)
+ await send(message)
+ }
+
+ return (
+
+ {optimisticMessages.map((m, i) => (
+
{m.message}
+ ))}
+
+
+ Enviar
+
+
+ )
+}
+```
+
+```jsx filename="app/page.js" switcher
+'use client'
+
+import { useOptimistic } from 'react'
+import { send } from './actions'
+
+export function Thread({ messages }) {
+ const [optimisticMessages, addOptimisticMessage] = useOptimistic(
+ messages,
+ (state, newMessage) => [...state, { message: newMessage }]
+ )
+
+ const formAction = async (formData) => {
+ const message = formData.get('message')
+ addOptimisticMessage(message)
+ await send(message)
+ }
+
+ return (
+
+ {optimisticMessages.map((m) => (
+
{m.message}
+ ))}
+
+
+ Enviar
+
+
+ )
+}
+```
+
+## Elementos anidados de formulario
+
+Puede llamar Acciones de Servidor en elementos anidados dentro de `` como ``, ` ` y ` `. Estos elementos aceptan la prop `formAction` o manejadores de eventos.
+
+Esto es útil en casos donde desea llamar múltiples Acciones de Servidor dentro de un formulario. Por ejemplo, puede crear un elemento `` específico para guardar un borrador de publicación además de publicarlo. Consulte la [documentación de `` de React](https://react.dev/reference/react-dom/components/form#handling-multiple-submission-types) para más información.
+
+## Envío programático de formularios
+
+Puede activar el envío de un formulario programáticamente usando el método [`requestSubmit()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/requestSubmit). Por ejemplo, cuando el usuario envía un formulario usando el atajo de teclado `⌘` + `Enter`, puede escuchar el evento `onKeyDown`:
+
+```tsx filename="app/entry.tsx" switcher
+'use client'
+
+export function Entry() {
+ const handleKeyDown = (e: React.KeyboardEvent) => {
+ if (
+ (e.ctrlKey || e.metaKey) &&
+ (e.key === 'Enter' || e.key === 'NumpadEnter')
+ ) {
+ e.preventDefault()
+ e.currentTarget.form?.requestSubmit()
+ }
+ }
+
+ return (
+
+
+
+ )
+}
+```
+
+```jsx filename="app/entry.js" switcher
+'use client'
+
+export function Entry() {
+ const handleKeyDown = (e) => {
+ if (
+ (e.ctrlKey || e.metaKey) &&
+ (e.key === 'Enter' || e.key === 'NumpadEnter')
+ ) {
+ e.preventDefault()
+ e.currentTarget.form?.requestSubmit()
+ }
+ }
+
+ return (
+
+
+
+ )
+}
+```
+
+Esto activará el envío del `` ancestro más cercano, lo que invocará la Función del Servidor.
\ No newline at end of file
diff --git a/apps/docs/content/es/docs/01-app/02-guides/incremental-static-regeneration.mdx b/apps/docs/content/es/docs/01-app/02-guides/incremental-static-regeneration.mdx
new file mode 100644
index 00000000..525a2c56
--- /dev/null
+++ b/apps/docs/content/es/docs/01-app/02-guides/incremental-static-regeneration.mdx
@@ -0,0 +1,614 @@
+---
+source-updated-at: 2025-06-01T01:32:20.000Z
+translation-updated-at: 2025-06-02T20:03:34.818Z
+title: Cómo implementar Regeneración Estática Incremental (ISR)
+nav_title: ISR
+description: Aprende a crear o actualizar páginas estáticas en tiempo de ejecución con Regeneración Estática Incremental.
+---
+
+
+ Ejemplos
+
+- [Next.js Commerce](https://vercel.com/templates/next.js/nextjs-commerce)
+- [ISR bajo demanda](https://on-demand-isr.vercel.app)
+- [Formularios en Next.js](https://github.com/vercel/next.js/tree/canary/examples/next-forms)
+
+
+
+La Regeneración Estática Incremental (ISR) permite:
+
+- Actualizar contenido estático sin reconstruir todo el sitio
+- Reducir la carga del servidor sirviendo páginas estáticas prerrenderizadas para la mayoría de solicitudes
+- Asegurar que los encabezados `cache-control` adecuados se añadan automáticamente a las páginas
+- Manejar grandes cantidades de páginas de contenido sin tiempos largos de `next build`
+
+Aquí un ejemplo mínimo:
+
+
+
+```tsx filename="app/blog/[id]/page.tsx" switcher
+interface Post {
+ id: string
+ title: string
+ content: string
+}
+
+// Next.js invalidará la caché cuando llegue una
+// solicitud, como máximo una vez cada 60 segundos.
+export const revalidate = 60
+
+// Prerrenderizaremos solo los parámetros de `generateStaticParams` en tiempo de compilación.
+// Si llega una solicitud para una ruta no generada,
+// Next.js renderizará la página en el servidor bajo demanda.
+export const dynamicParams = true // o false, para mostrar 404 en rutas desconocidas
+
+export async function generateStaticParams() {
+ const posts: Post[] = await fetch('https://api.vercel.app/blog').then((res) =>
+ res.json()
+ )
+ return posts.map((post) => ({
+ id: String(post.id),
+ }))
+}
+
+export default async function Page({
+ params,
+}: {
+ params: Promise<{ id: string }>
+}) {
+ const { id } = await params
+ const post: Post = await fetch(`https://api.vercel.app/blog/${id}`).then(
+ (res) => res.json()
+ )
+ return (
+
+ {post.title}
+ {post.content}
+
+ )
+}
+```
+
+```jsx filename="app/blog/[id]/page.js" switcher
+// Next.js invalidará la caché cuando llegue una
+// solicitud, como máximo una vez cada 60 segundos.
+export const revalidate = 60
+
+// Prerrenderizaremos solo los parámetros de `generateStaticParams` en tiempo de compilación.
+// Si llega una solicitud para una ruta no generada,
+// Next.js renderizará la página en el servidor bajo demanda.
+export const dynamicParams = true // o false, para mostrar 404 en rutas desconocidas
+
+export async function generateStaticParams() {
+ const posts = await fetch('https://api.vercel.app/blog').then((res) =>
+ res.json()
+ )
+ return posts.map((post) => ({
+ id: String(post.id),
+ }))
+}
+
+export default async function Page({ params }) {
+ const { id } = await params
+ const post = await fetch(`https://api.vercel.app/blog/${id}`).then((res) =>
+ res.json()
+ )
+ return (
+
+ {post.title}
+ {post.content}
+
+ )
+}
+```
+
+
+
+
+
+```tsx filename="pages/blog/[id].tsx" switcher
+import type { GetStaticPaths, GetStaticProps } from 'next'
+
+interface Post {
+ id: string
+ title: string
+ content: string
+}
+
+interface Props {
+ post: Post
+}
+
+export const getStaticPaths: GetStaticPaths = async () => {
+ const posts = await fetch('https://api.vercel.app/blog').then((res) =>
+ res.json()
+ )
+ const paths = posts.map((post: Post) => ({
+ params: { id: String(post.id) },
+ }))
+
+ // Prerrenderizaremos solo estas rutas en tiempo de compilación.
+ // { fallback: 'blocking' } renderizará páginas en el servidor
+ // bajo demanda si la ruta no existe.
+ return { paths, fallback: false }
+}
+
+export const getStaticProps: GetStaticProps = async ({
+ params,
+}: {
+ params: { id: string }
+}) => {
+ const post = await fetch(`https://api.vercel.app/blog/${params.id}`).then(
+ (res) => res.json()
+ )
+
+ return {
+ props: { post },
+ // Next.js invalidará la caché cuando llegue una
+ // solicitud, como máximo una vez cada 60 segundos.
+ revalidate: 60,
+ }
+}
+
+export default function Page({ post }: Props) {
+ return (
+
+ {post.title}
+ {post.content}
+
+ )
+}
+```
+
+```jsx filename="pages/blog/[id].jsx" switcher
+export async function getStaticPaths() {
+ const posts = await fetch('https://api.vercel.app/blog').then((res) =>
+ res.json()
+ )
+ const paths = posts.map((post) => ({
+ params: { id: post.id },
+ }))
+
+ // Prerrenderizaremos solo estas rutas en tiempo de compilación.
+ // { fallback: false } significa que otras rutas mostrarán 404.
+ return { paths, fallback: false }
+}
+
+export async function getStaticProps({ params }) {
+ const post = await fetch(`https://api.vercel.app/blog/${params.id}`).then(
+ (res) => res.json()
+ )
+
+ return {
+ props: { post },
+ // Next.js invalidará la caché cuando llegue una
+ // solicitud, como máximo una vez cada 60 segundos.
+ revalidate: 60,
+ }
+}
+
+export default function Page({ post }) {
+ return (
+
+ {post.title}
+ {post.content}
+
+ )
+}
+```
+
+
+
+Así funciona este ejemplo:
+
+1. Durante `next build`, se generan todas las entradas de blog conocidas (hay 25 en este ejemplo)
+2. Todas las solicitudes a estas páginas (ej. `/blog/1`) se almacenan en caché y son instantáneas
+3. Después de 60 segundos, la siguiente solicitud mostrará la página en caché (obsoleta)
+4. La caché se invalida y comienza a generarse una nueva versión de la página en segundo plano
+5. Una vez generada con éxito, Next.js mostrará y almacenará en caché la página actualizada
+6. Si se solicita `/blog/26`, Next.js generará y almacenará esta página bajo demanda
+
+## Referencia
+
+
+
+### Configuración del segmento de ruta
+
+- [`revalidate`](/docs/app/api-reference/file-conventions/route-segment-config#revalidate)
+- [`dynamicParams`](/docs/app/api-reference/file-conventions/route-segment-config#dynamicparams)
+
+### Funciones
+
+- [`revalidatePath`](/docs/app/api-reference/functions/revalidatePath)
+- [`revalidateTag`](/docs/app/api-reference/functions/revalidateTag)
+
+
+
+
+
+### Funciones
+
+- [`getStaticProps`](/docs/pages/building-your-application/data-fetching/get-static-props)
+- [`res.revalidate`](/docs/pages/building-your-application/routing/api-routes#response-helpers)
+
+
+
+## Ejemplos
+
+
+
+### Revalidación basada en tiempo
+
+Esto obtiene y muestra una lista de entradas de blog en `/blog`. Después de una hora, la caché para esta página se invalida en la próxima visita. Luego, en segundo plano, se genera una nueva versión con las entradas más recientes.
+
+```tsx filename="app/blog/page.tsx" switcher
+interface Post {
+ id: string
+ title: string
+ content: string
+}
+
+export const revalidate = 3600 // invalida cada hora
+
+export default async function Page() {
+ const data = await fetch('https://api.vercel.app/blog')
+ const posts: Post[] = await data.json()
+ return (
+
+ Entradas del Blog
+
+ {posts.map((post) => (
+ {post.title}
+ ))}
+
+
+ )
+}
+```
+
+```jsx filename="app/blog/page.js" switcher
+export const revalidate = 3600 // invalida cada hora
+
+export default async function Page() {
+ const data = await fetch('https://api.vercel.app/blog')
+ const posts = await data.json()
+ return (
+
+ Entradas del Blog
+
+ {posts.map((post) => (
+ {post.title}
+ ))}
+
+
+ )
+}
+```
+
+Recomendamos establecer un tiempo alto de revalidación. Por ejemplo, 1 hora en lugar de 1 segundo. Si necesita más precisión, considere usar revalidación bajo demanda. Para datos en tiempo real, considere cambiar a [renderizado dinámico](/docs/app/getting-started/partial-prerendering#dynamic-rendering).
+
+### Revalidación bajo demanda con `revalidatePath`
+
+Para un método más preciso de revalidación, invalide páginas bajo demanda con la función `revalidatePath`.
+
+Por ejemplo, esta Acción de Servidor se llamaría después de agregar una nueva entrada. Independientemente de cómo obtenga los datos en su Componente de Servidor, ya sea usando `fetch` o conectando a una base de datos, esto limpiará la caché para toda la ruta y permitirá al Componente de Servidor obtener datos frescos.
+
+```ts filename="app/actions.ts" switcher
+'use server'
+
+import { revalidatePath } from 'next/cache'
+
+export async function createPost() {
+ // Invalida la ruta /posts en la caché
+ revalidatePath('/posts')
+}
+```
+
+```js filename="app/actions.js" switcher
+'use server'
+
+import { revalidatePath } from 'next/cache'
+
+export async function createPost() {
+ // Invalida la ruta /posts en la caché
+ revalidatePath('/posts')
+}
+```
+
+[Ver demo](https://on-demand-isr.vercel.app) y [explorar código fuente](https://github.com/vercel/on-demand-isr).
+
+### Revalidación bajo demanda con `revalidateTag`
+
+Para la mayoría de casos, prefiera revalidar rutas completas. Si necesita control más granular, use `revalidateTag`. Por ejemplo, puede etiquetar llamadas `fetch` individuales:
+
+```tsx filename="app/blog/page.tsx" switcher
+export default async function Page() {
+ const data = await fetch('https://api.vercel.app/blog', {
+ next: { tags: ['posts'] },
+ })
+ const posts = await data.json()
+ // ...
+}
+```
+
+```jsx filename="app/blog/page.js" switcher
+export default async function Page() {
+ const data = await fetch('https://api.vercel.app/blog', {
+ next: { tags: ['posts'] },
+ })
+ const posts = await data.json()
+ // ...
+}
+```
+
+Si usa un ORM o conecta a una base de datos, puede usar `unstable_cache`:
+
+```tsx filename="app/blog/page.tsx" switcher
+import { unstable_cache } from 'next/cache'
+import { db, posts } from '@/lib/db'
+
+const getCachedPosts = unstable_cache(
+ async () => {
+ return await db.select().from(posts)
+ },
+ ['posts'],
+ { revalidate: 3600, tags: ['posts'] }
+)
+
+export default async function Page() {
+ const posts = getCachedPosts()
+ // ...
+}
+```
+
+```jsx filename="app/blog/page.js" switcher
+import { unstable_cache } from 'next/cache'
+import { db, posts } from '@/lib/db'
+
+const getCachedPosts = unstable_cache(
+ async () => {
+ return await db.select().from(posts)
+ },
+ ['posts'],
+ { revalidate: 3600, tags: ['posts'] }
+)
+
+export default async function Page() {
+ const posts = getCachedPosts()
+ // ...
+}
+```
+
+Luego puede usar `revalidateTag` en [Acciones de Servidor](/docs/app/building-your-application/data-fetching/server-actions-and-mutations) o [Manejadores de Ruta](/docs/app/building-your-application/routing/route-handlers):
+
+```ts filename="app/actions.ts" switcher
+'use server'
+
+import { revalidateTag } from 'next/cache'
+
+export async function createPost() {
+ // Invalida todos los datos etiquetados con 'posts' en la caché
+ revalidateTag('posts')
+}
+```
+
+```js filename="app/actions.js" switcher
+'use server'
+
+import { revalidateTag } from 'next/cache'
+
+export async function createPost() {
+ // Invalida todos los datos etiquetados con 'posts' en la caché
+ revalidateTag('posts')
+}
+```
+
+
+
+
+
+### Validación bajo demanda con `res.revalidate()`
+
+Para un método más preciso de revalidación, use `res.revalidate` para generar una nueva página bajo demanda desde un Enrutador API.
+
+Por ejemplo, esta Ruta API puede llamarse en `/api/revalidate?secret=` para revalidar una entrada de blog. Cree un token secreto solo conocido por su app Next.js. Este secreto evitará acceso no autorizado a la Ruta API de revalidación.
+
+```ts filename="pages/api/revalidate.ts" switcher
+import type { NextApiRequest, NextApiResponse } from 'next'
+
+export default async function handler(
+ req: NextApiRequest,
+ res: NextApiResponse
+) {
+ // Verificar secreto para confirmar solicitud válida
+ if (req.query.secret !== process.env.MY_SECRET_TOKEN) {
+ return res.status(401).json({ message: 'Token inválido' })
+ }
+
+ try {
+ // Esta debe ser la ruta real no reescrita
+ // ej. para "/posts/[id]" debe ser "/posts/1"
+ await res.revalidate('/posts/1')
+ return res.json({ revalidated: true })
+ } catch (err) {
+ // Si hay error, Next.js continuará mostrando
+ // la última página generada exitosamente
+ return res.status(500).send('Error al revalidar')
+ }
+}
+```
+
+```js filename="pages/api/revalidate.js" switcher
+export default async function handler(req, res) {
+ // Verificar secreto para confirmar solicitud válida
+ if (req.query.secret !== process.env.MY_SECRET_TOKEN) {
+ return res.status(401).json({ message: 'Token inválido' })
+ }
+
+ try {
+ // Esta debe ser la ruta real no reescrita
+ // ej. para "/posts/[id]" debe ser "/posts/1"
+ await res.revalidate('/posts/1')
+ return res.json({ revalidated: true })
+ } catch (err) {
+ // Si hay error, Next.js continuará mostrando
+ // la última página generada exitosamente
+ return res.status(500).send('Error al revalidar')
+ }
+}
+```
+
+Si usa revalidación bajo demanda, no necesita especificar un tiempo `revalidate` en `getStaticProps`. Next.js usará el valor por defecto `false` (sin revalidación) y solo revalidará la página bajo demanda cuando se llame a `res.revalidate()`.
+
+
+
+### Manejo de excepciones no capturadas
+
+
+
+Si ocurre un error al intentar revalidar datos, se seguirán sirviendo los últimos datos generados exitosamente desde la caché. En la siguiente solicitud, Next.js reintentará revalidar. [Más sobre manejo de errores](/docs/app/getting-started/error-handling).
+
+
+
+
+
+Si hay un error dentro de `getStaticProps` durante regeneración en segundo plano, o lanza un error manualmente, se seguirá mostrando la última página generada exitosamente. En la siguiente solicitud, Next.js reintentará llamar a `getStaticProps`.
+
+```tsx filename="pages/blog/[id].tsx" switcher
+import type { GetStaticProps } from 'next'
+
+interface Post {
+ id: string
+ title: string
+ content: string
+}
+
+interface Props {
+ post: Post
+}
+
+export const getStaticProps: GetStaticProps = async ({
+ params,
+}: {
+ params: { id: string }
+}) => {
+ // Si esta solicitud lanza un error no capturado, Next.js no
+ // invalidará la página actualmente mostrada y
+ // reintentará getStaticProps en la próxima solicitud.
+ const res = await fetch(`https://api.vercel.app/blog/${params.id}`)
+ const post: Post = await res.json()
+
+ if (!res.ok) {
+ // Si hay error del servidor, puede lanzar un error
+ // en lugar de retornar para que la caché no se actualice
+ // hasta la próxima solicitud exitosa.
+ throw new Error(`Error al obtener entradas, estado recibido ${res.status}`)
+ }
+
+ return {
+ props: { post },
+ // Next.js invalidará la caché cuando llegue una
+ // solicitud, como máximo una vez cada 60 segundos.
+ revalidate: 60,
+ }
+}
+```
+
+```jsx filename="pages/blog/[id].jsx" switcher
+export async function getStaticProps({ params }) {
+ // Si esta solicitud lanza un error no capturado, Next.js no
+ // invalidará la página actualmente mostrada y
+ // reintentará getStaticProps en la próxima solicitud.
+ const res = await fetch(`https://api.vercel.app/blog/${params.id}`)
+ const post = await res.json()
+
+ if (!res.ok) {
+ // Si hay error del servidor, puede lanzar un error
+ // en lugar de retornar para que la caché no se actualice
+ // hasta la próxima solicitud exitosa.
+ throw new Error(`Error al obtener entradas, estado recibido ${res.status}`)
+ }
+
+ return {
+ props: { post },
+ // Next.js invalidará la caché cuando llegue una
+ // solicitud, como máximo una vez cada 60 segundos.
+ revalidate: 60,
+ }
+}
+```
+
+
+
+### Personalizando la ubicación de la caché
+
+Puede configurar la ubicación de la caché de Next.js si desea persistir páginas y datos en almacenamiento duradero, o compartir la caché entre múltiples contenedores o instancias de su aplicación. [Más información](/docs/app/guides/self-hosting#caching-and-isr).
+
+## Solución de problemas
+
+### Depuración de datos en caché en desarrollo local
+
+Si usa la API `fetch`, puede añadir registro adicional para entender qué solicitudes están en caché o no. [Más sobre la opción `logging`](/docs/app/api-reference/config/next-config-js/logging).
+
+```jsx filename="next.config.js"
+module.exports = {
+ logging: {
+ fetches: {
+ fullUrl: true,
+ },
+ },
+}
+```
+
+### Verificar el comportamiento correcto en producción
+
+Para verificar que sus páginas se almacenan en caché y se revalidan correctamente en producción, puede probar localmente ejecutando `next build` y luego `next start` para ejecutar el servidor de producción de Next.js.
+
+Esto le permitirá probar el comportamiento de ISR (Regeneración Incremental Estática) como funcionaría en un entorno de producción. Para depuración adicional, agregue la siguiente variable de entorno a su archivo `.env`:
+
+```bash filename=".env"
+NEXT_PRIVATE_DEBUG_CACHE=1
+```
+
+Esto hará que el servidor de Next.js registre en la consola los aciertos y fallos de la caché ISR. Puede inspeccionar la salida para ver qué páginas se generan durante `next build`, así como cómo se actualizan las páginas cuando se accede a las rutas bajo demanda.
+
+## Consideraciones
+
+
+
+- ISR solo es compatible cuando se utiliza el entorno de ejecución Node.js (predeterminado).
+- ISR no es compatible al crear una [Exportación Estática](/docs/app/guides/static-exports).
+- Si tiene múltiples solicitudes `fetch` en una ruta renderizada estáticamente, y cada una tiene una frecuencia de `revalidate` diferente, se utilizará el tiempo más bajo para ISR. Sin embargo, esas frecuencias de revalidación seguirán siendo respetadas por la [Caché de Datos](/docs/app/deep-dive/caching#data-cache).
+- Si alguna de las solicitudes `fetch` utilizadas en una ruta tiene un tiempo de `revalidate` de `0`, o un `no-store` explícito, la ruta se [renderizará dinámicamente](/docs/app/getting-started/partial-prerendering#dynamic-rendering).
+- El Middleware no se ejecutará para solicitudes ISR bajo demanda, lo que significa que cualquier reescritura de ruta o lógica en el Middleware no se aplicará. Asegúrese de revalidar la ruta exacta. Por ejemplo, `/post/1` en lugar de una reescritura `/post-1`.
+
+
+
+
+
+- ISR solo es compatible cuando se utiliza el entorno de ejecución Node.js (predeterminado).
+- ISR no es compatible al crear una [Exportación Estática](/docs/app/guides/static-exports).
+- El Middleware no se ejecutará para solicitudes ISR bajo demanda, lo que significa que cualquier reescritura de ruta o lógica en el Middleware no se aplicará. Asegúrese de revalidar la ruta exacta. Por ejemplo, `/post/1` en lugar de una reescritura `/post-1`.
+
+
+
+## Compatibilidad con plataformas
+
+| Opción de despliegue | Compatible |
+| ------------------------------------------------------------------- | ------------------ |
+| [Servidor Node.js](/docs/app/getting-started/deploying#nodejs-server) | Sí |
+| [Contenedor Docker](/docs/app/getting-started/deploying#docker) | Sí |
+| [Exportación estática](/docs/app/getting-started/deploying#static-export) | No |
+| [Adaptadores](/docs/app/getting-started/deploying#adapters) | Depende de la plataforma |
+
+Aprenda cómo [configurar ISR](/docs/app/guides/self-hosting#caching-and-isr) al alojar Next.js usted mismo.
+
+## Historial de versiones
+
+| Versión | Cambios |
+| --------- | ----------------------------------------------------------------------------------- |
+| `v14.1.0` | El `cacheHandler` personalizado es estable. |
+| `v13.0.0` | Se introduce el App Router. |
+| `v12.2.0` | Pages Router: ISR bajo demanda es estable |
+| `v12.0.0` | Pages Router: Se añade [ISR con detección de bots](/blog/next-12#bot-aware-isr-fallback). |
+| `v9.5.0` | Pages Router: [Se introduce ISR estable](/blog/next-9-5). |
diff --git a/apps/docs/content/es/docs/01-app/02-guides/index.mdx b/apps/docs/content/es/docs/01-app/02-guides/index.mdx
new file mode 100644
index 00000000..25226a74
--- /dev/null
+++ b/apps/docs/content/es/docs/01-app/02-guides/index.mdx
@@ -0,0 +1,65 @@
+---
+source-updated-at: 2025-06-01T01:32:20.000Z
+translation-updated-at: 2025-06-02T20:00:21.735Z
+title: Guías
+description: Aprenda a implementar patrones comunes de UI y casos de uso utilizando Next.js
+---
+
+### Obtención de datos (Data Fetching)
+
+- [Uso de la API `fetch`](/docs/app/getting-started/fetching-data#with-the-fetch-api)
+- [Uso de un ORM o cliente de base de datos](/docs/app/getting-started/fetching-data#with-an-orm-or-database)
+- [Lectura de parámetros de búsqueda en el servidor](/docs/app/api-reference/file-conventions/page)
+- [Lectura de parámetros de búsqueda en el cliente](/docs/app/api-reference/functions/use-search-params)
+
+### Revalidación de datos
+
+- [Uso de ISR para revalidar datos después de un tiempo determinado](/docs/app/guides/incremental-static-regeneration#time-based-revalidation)
+- [Uso de ISR para revalidar datos bajo demanda](/docs/app/guides/incremental-static-regeneration#on-demand-revalidation-with-revalidatepath)
+
+### Formularios
+
+- [Mostrar un estado pendiente al enviar un formulario](/docs/app/guides/forms)
+- [Validación de formularios en el servidor](/docs/app/guides/forms)
+- [Manejo de errores esperados](/docs/app/getting-started/error-handling#handling-expected-errors)
+- [Manejo de excepciones inesperadas](/docs/app/getting-started/error-handling#handling-uncaught-exceptions)
+- [Mostrar actualizaciones optimistas de UI](/docs/app/guides/forms#optimistic-updates)
+- [Envío programático de formularios](/docs/app/guides/forms#programmatic-form-submission)
+
+### Acciones de servidor (Server Actions)
+
+- [Paso de valores adicionales](/docs/app/guides/forms)
+- [Revalidación de datos](/docs/app/building-your-application/data-fetching/server-actions-and-mutations#revalidating-data)
+- [Redireccionamiento](/docs/app/building-your-application/data-fetching/server-actions-and-mutations#redirecting)
+- [Configuración de cookies](/docs/app/api-reference/functions/cookies#setting-a-cookie)
+- [Eliminación de cookies](/docs/app/api-reference/functions/cookies#deleting-cookies)
+
+### Metadatos
+
+- [Creación de un feed RSS](/docs/app/building-your-application/routing/route-handlers#non-ui-responses)
+- [Creación de una imagen Open Graph](/docs/app/api-reference/file-conventions/metadata/opengraph-image)
+- [Creación de un sitemap](/docs/app/api-reference/file-conventions/metadata/sitemap)
+- [Creación de un archivo robots.txt](/docs/app/api-reference/file-conventions/metadata/robots)
+- [Creación de una página 404 personalizada](/docs/app/api-reference/file-conventions/not-found)
+- [Creación de una página 500 personalizada](/docs/app/api-reference/file-conventions/error)
+
+### Autenticación (Auth)
+
+- [Creación de un formulario de registro](/docs/app/guides/authentication#sign-up-and-login-functionality)
+- [Gestión de sesiones sin estado basadas en cookies](/docs/app/guides/authentication#stateless-sessions)
+- [Gestión de sesiones con estado respaldadas por base de datos](/docs/app/guides/authentication#database-sessions)
+- [Gestión de autorización](/docs/app/guides/authentication#authorization)
+
+### Pruebas (Testing)
+
+- [Vitest](/docs/app/guides/testing/vitest)
+- [Jest](/docs/app/guides/testing/jest)
+- [Playwright](/docs/app/guides/testing/playwright)
+- [Cypress](/docs/app/guides/testing/cypress)
+
+### Despliegue (Deployment)
+
+- [Creación de un Dockerfile](/docs/app/getting-started/deploying#docker)
+- [Creación de una exportación estática (SPA)](/docs/app/guides/static-exports)
+- [Configuración de caché al autoalojar](/docs/app/guides/self-hosting#configuring-caching)
+- [Configuración de optimización de imágenes al autoalojar](/docs/app/guides/self-hosting#image-optimization)
\ No newline at end of file
diff --git a/apps/docs/content/es/docs/01-app/02-guides/instrumentation.mdx b/apps/docs/content/es/docs/01-app/02-guides/instrumentation.mdx
new file mode 100644
index 00000000..e76ec5dd
--- /dev/null
+++ b/apps/docs/content/es/docs/01-app/02-guides/instrumentation.mdx
@@ -0,0 +1,98 @@
+---
+source-updated-at: 2025-05-19T22:31:51.000Z
+translation-updated-at: 2025-06-02T20:00:30.135Z
+title: Cómo configurar la instrumentación
+nav_title: Instrumentación
+description: Aprenda a usar la instrumentación para ejecutar código al iniciar el servidor en su aplicación Next.js
+related:
+ title: Más información sobre Instrumentación
+ links:
+ - app/api-reference/file-conventions/instrumentation
+---
+
+{/* El contenido de este documento se comparte entre el enrutador de la aplicación y el de 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. */}
+
+La instrumentación es el proceso de usar código para integrar herramientas de monitoreo y registro en su aplicación. Esto le permite rastrear el rendimiento y comportamiento de su aplicación, así como depurar problemas en producción.
+
+## Convención
+
+Para configurar la instrumentación, cree un archivo `instrumentation.ts|js` en el **directorio raíz** de su proyecto (o dentro de la carpeta [`src`](/docs/app/api-reference/file-conventions/src-folder) si está utilizando una).
+
+Luego, exporte una función `register` en el archivo. Esta función será llamada **una vez** cuando se inicie una nueva instancia del servidor Next.js.
+
+Por ejemplo, para usar Next.js con [OpenTelemetry](https://opentelemetry.io/) y [@vercel/otel](https://vercel.com/docs/observability/otel-overview):
+
+```ts filename="instrumentation.ts" switcher
+import { registerOTel } from '@vercel/otel'
+
+export function register() {
+ registerOTel('next-app')
+}
+```
+
+```js filename="instrumentation.js" switcher
+import { registerOTel } from '@vercel/otel'
+
+export function register() {
+ registerOTel('next-app')
+}
+```
+
+Consulte el [ejemplo de Next.js con OpenTelemetry](https://github.com/vercel/next.js/tree/canary/examples/with-opentelemetry) para una implementación completa.
+
+> **Es bueno saberlo**:
+>
+> - El archivo `instrumentation` debe estar en la raíz de su proyecto y no dentro de los directorios `app` o `pages`. Si está utilizando la carpeta `src`, coloque el archivo dentro de `src` junto con `pages` y `app`.
+> - Si utiliza la opción de configuración [`pageExtensions`](/docs/app/api-reference/config/next-config-js/pageExtensions) para agregar un sufijo, también deberá actualizar el nombre del archivo `instrumentation` para que coincida.
+
+## Ejemplos
+
+### Importar archivos con efectos secundarios
+
+A veces puede ser útil importar un archivo en su código debido a los efectos secundarios que causará. Por ejemplo, podría importar un archivo que define un conjunto de variables globales, pero nunca usar explícitamente el archivo importado en su código. Aún así tendría acceso a las variables globales que el paquete ha declarado.
+
+Recomendamos importar archivos usando la sintaxis de `import` de JavaScript dentro de su función `register`. El siguiente ejemplo demuestra un uso básico de `import` en una función `register`:
+
+```ts filename="instrumentation.ts" switcher
+export async function register() {
+ await import('package-with-side-effect')
+}
+```
+
+```js filename="instrumentation.js" switcher
+export async function register() {
+ await import('package-with-side-effect')
+}
+```
+
+> **Es bueno saberlo:**
+>
+> Recomendamos importar el archivo desde dentro de la función `register`, en lugar de hacerlo en la parte superior del archivo. Al hacer esto, puede agrupar todos sus efectos secundarios en un solo lugar en su código y evitar consecuencias no deseadas al importar globalmente en la parte superior del archivo.
+
+### Importar código específico del entorno de ejecución
+
+Next.js llama a `register` en todos los entornos, por lo que es importante importar condicionalmente cualquier código que no admita entornos de ejecución específicos (por ejemplo, [Edge o Node.js](/docs/app/api-reference/edge)). Puede usar la variable de entorno `NEXT_RUNTIME` para obtener el entorno actual:
+
+```ts filename="instrumentation.ts" switcher
+export async function register() {
+ if (process.env.NEXT_RUNTIME === 'nodejs') {
+ await import('./instrumentation-node')
+ }
+
+ if (process.env.NEXT_RUNTIME === 'edge') {
+ await import('./instrumentation-edge')
+ }
+}
+```
+
+```js filename="instrumentation.js" switcher
+export async function register() {
+ if (process.env.NEXT_RUNTIME === 'nodejs') {
+ await import('./instrumentation-node')
+ }
+
+ if (process.env.NEXT_RUNTIME === 'edge') {
+ await import('./instrumentation-edge')
+ }
+}
+```
\ No newline at end of file
diff --git a/apps/docs/content/es/docs/01-app/02-guides/internationalization.mdx b/apps/docs/content/es/docs/01-app/02-guides/internationalization.mdx
new file mode 100644
index 00000000..8a5ac363
--- /dev/null
+++ b/apps/docs/content/es/docs/01-app/02-guides/internationalization.mdx
@@ -0,0 +1,218 @@
+---
+source-updated-at: 2025-05-21T18:33:43.000Z
+translation-updated-at: 2025-06-02T20:01:04.885Z
+title: Internacionalización
+description: Agrega soporte para múltiples idiomas con enrutamiento internacionalizado y contenido localizado.
+---
+
+Next.js te permite configurar el enrutamiento y renderizado de contenido para soportar múltiples idiomas. Hacer que tu sitio se adapte a diferentes localizaciones incluye contenido traducido (localización) y rutas internacionalizadas.
+
+## Terminología
+
+- **Localización (Locale):** Un identificador para un conjunto de preferencias de idioma y formato. Esto generalmente incluye el idioma preferido del usuario y posiblemente su región geográfica.
+ - `en-US`: Inglés como se habla en Estados Unidos
+ - `nl-NL`: Holandés como se habla en Países Bajos
+ - `nl`: Holandés, sin región específica
+
+## Visión general del enrutamiento
+
+Se recomienda usar las preferencias de idioma del usuario en el navegador para seleccionar qué localización usar. Cambiar tu idioma preferido modificará el encabezado `Accept-Language` que llega a tu aplicación.
+
+Por ejemplo, usando las siguientes bibliotecas, puedes examinar una `Request` entrante para determinar qué localización seleccionar, basado en los `Headers`, las localizaciones que planeas soportar y la localización predeterminada.
+
+```js filename="middleware.js"
+import { match } from '@formatjs/intl-localematcher'
+import Negotiator from 'negotiator'
+
+let headers = { 'accept-language': 'en-US,en;q=0.5' }
+let languages = new Negotiator({ headers }).languages()
+let locales = ['en-US', 'nl-NL', 'nl']
+let defaultLocale = 'en-US'
+
+match(languages, locales, defaultLocale) // -> 'en-US'
+```
+
+El enrutamiento puede internacionalizarse mediante sub-rutas (`/fr/products`) o dominios (`my-site.fr/products`). Con esta información, ahora puedes redirigir al usuario basado en la localización dentro de [Middleware](/docs/app/building-your-application/routing/middleware).
+
+```js filename="middleware.js"
+import { NextResponse } from "next/server";
+
+let locales = ['en-US', 'nl-NL', 'nl']
+
+// Obtener la localización preferida, similar al ejemplo anterior o usando una biblioteca
+function getLocale(request) { ... }
+
+export function middleware(request) {
+ // Verificar si hay alguna localización soportada en el pathname
+ const { pathname } = request.nextUrl
+ const pathnameHasLocale = locales.some(
+ (locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
+ )
+
+ if (pathnameHasLocale) return
+
+ // Redirigir si no hay localización
+ const locale = getLocale(request)
+ request.nextUrl.pathname = `/${locale}${pathname}`
+ // Ejemplo: solicitud entrante es /products
+ // La nueva URL ahora es /en-US/products
+ return NextResponse.redirect(request.nextUrl)
+}
+
+export const config = {
+ matcher: [
+ // Omitir todas las rutas internas (_next)
+ '/((?!_next).*)',
+ // Opcional: ejecutar solo en la URL raíz (/)
+ // '/'
+ ],
+}
+```
+
+Finalmente, asegúrate que todos los archivos especiales dentro de `app/` estén anidados bajo `app/[lang]`. Esto permite al enrutador de Next.js manejar dinámicamente diferentes localizaciones en la ruta, y pasar el parámetro `lang` a cada layout y página. Por ejemplo:
+
+```tsx filename="app/[lang]/page.tsx" switcher
+// Ahora tienes acceso a la localización actual
+// Ejemplo: /en-US/products -> `lang` es "en-US"
+export default async function Page({
+ params,
+}: {
+ params: Promise<{ lang: string }>
+}) {
+ const { lang } = await params
+ return ...
+}
+```
+
+```jsx filename="app/[lang]/page.js" switcher
+// Ahora tienes acceso a la localización actual
+// Ejemplo: /en-US/products -> `lang` es "en-US"
+export default async function Page({ params }) {
+ const { lang } = await params
+ return ...
+}
+```
+
+El layout raíz también puede anidarse en la nueva carpeta (ej. `app/[lang]/layout.js`).
+
+## Localización
+
+Cambiar el contenido mostrado basado en la localización preferida del usuario, o localización, no es algo específico de Next.js. Los patrones descritos a continuación funcionarían igual con cualquier aplicación web.
+
+Supongamos que queremos soportar contenido tanto en inglés como en holandés dentro de nuestra aplicación. Podríamos mantener dos "diccionarios" diferentes, que son objetos que nos dan un mapeo de alguna clave a una cadena localizada. Por ejemplo:
+
+```json filename="dictionaries/en.json"
+{
+ "products": {
+ "cart": "Add to Cart"
+ }
+}
+```
+
+```json filename="dictionaries/nl.json"
+{
+ "products": {
+ "cart": "Toevoegen aan Winkelwagen"
+ }
+}
+```
+
+Luego podemos crear una función `getDictionary` para cargar las traducciones para la localización solicitada:
+
+```ts filename="app/[lang]/dictionaries.ts" switcher
+import 'server-only'
+
+const dictionaries = {
+ en: () => import('./dictionaries/en.json').then((module) => module.default),
+ nl: () => import('./dictionaries/nl.json').then((module) => module.default),
+}
+
+export const getDictionary = async (locale: 'en' | 'nl') =>
+ dictionaries[locale]()
+```
+
+```js filename="app/[lang]/dictionaries.js" switcher
+import 'server-only'
+
+const dictionaries = {
+ en: () => import('./dictionaries/en.json').then((module) => module.default),
+ nl: () => import('./dictionaries/nl.json').then((module) => module.default),
+}
+
+export const getDictionary = async (locale) => dictionaries[locale]()
+```
+
+Dado el idioma actualmente seleccionado, podemos obtener el diccionario dentro de un layout o página.
+
+```tsx filename="app/[lang]/page.tsx" switcher
+import { getDictionary } from './dictionaries'
+
+export default async function Page({
+ params,
+}: {
+ params: Promise<{ lang: 'en' | 'nl' }>
+}) {
+ const { lang } = await params
+ const dict = await getDictionary(lang) // en
+ return {dict.products.cart} // Add to Cart
+}
+```
+
+```jsx filename="app/[lang]/page.js" switcher
+import { getDictionary } from './dictionaries'
+
+export default async function Page({ params }) {
+ const { lang } = await params
+ const dict = await getDictionary(lang) // en
+ return {dict.products.cart} // Add to Cart
+}
+```
+
+Debido a que todos los layouts y páginas en el directorio `app/` son por defecto [Componentes del Servidor](/docs/app/getting-started/server-and-client-components), no necesitamos preocuparnos por el tamaño de los archivos de traducción afectando el tamaño del paquete JavaScript del lado del cliente. Este código **solo se ejecutará en el servidor**, y solo el HTML resultante se enviará al navegador.
+
+## Renderizado estático
+
+Para generar rutas estáticas para un conjunto dado de localizaciones, podemos usar `generateStaticParams` con cualquier página o layout. Esto puede ser global, por ejemplo, en el layout raíz:
+
+```tsx filename="app/[lang]/layout.tsx" switcher
+export async function generateStaticParams() {
+ return [{ lang: 'en-US' }, { lang: 'de' }]
+}
+
+export default async function RootLayout({
+ children,
+ params,
+}: Readonly<{
+ children: React.ReactNode
+ params: Promise<{ lang: 'en-US' | 'de' }>
+}>) {
+ return (
+
+ {children}
+
+ )
+}
+```
+
+```jsx filename="app/[lang]/layout.js" switcher
+export async function generateStaticParams() {
+ return [{ lang: 'en-US' }, { lang: 'de' }]
+}
+
+export default async function RootLayout({ children, params }) {
+ return (
+
+ {children}
+
+ )
+}
+```
+
+## Recursos
+
+- [Enrutamiento i18n y traducciones mínimas](https://github.com/vercel/next.js/tree/canary/examples/i18n-routing)
+- [`next-intl`](https://next-intl.dev)
+- [`next-international`](https://github.com/QuiiBz/next-international)
+- [`next-i18n-router`](https://github.com/i18nexus/next-i18n-router)
+- [`paraglide-next`](https://inlang.com/m/osslbuzt/paraglide-next-i18n)
+- [`lingui`](https://lingui.dev)
\ No newline at end of file
diff --git a/apps/docs/content/es/docs/01-app/02-guides/json-ld.mdx b/apps/docs/content/es/docs/01-app/02-guides/json-ld.mdx
new file mode 100644
index 00000000..fdb78872
--- /dev/null
+++ b/apps/docs/content/es/docs/01-app/02-guides/json-ld.mdx
@@ -0,0 +1,87 @@
+---
+source-updated-at: 2025-06-01T01:32:20.000Z
+translation-updated-at: 2025-06-02T20:00:09.338Z
+title: Cómo implementar JSON-LD en tu aplicación Next.js
+nav_title: JSON-LD
+description: Aprende a añadir JSON-LD a tu aplicación Next.js para describir tu contenido a motores de búsqueda e inteligencia artificial.
+---
+
+[JSON-LD](https://json-ld.org/) es un formato para datos estructurados que pueden ser utilizados por motores de búsqueda e inteligencia artificial para ayudarles a entender la estructura de la página más allá del contenido puro. Por ejemplo, puedes usarlo para describir una persona, un evento, una organización, una película, un libro, una receta y muchos otros tipos de entidades.
+
+Nuestra recomendación actual para JSON-LD es renderizar los datos estructurados como una etiqueta `
+```
+
+O usando la propiedad `dangerouslySetInnerHTML`:
+
+```jsx
+
+```
+
+> **Advertencia**: Se debe asignar una propiedad `id` a los scripts en línea para que Next.js pueda rastrear y optimizar el script.
+
+### Ejecutar Código Adicional
+
+Los manejadores de eventos se pueden usar con el componente Script para ejecutar código adicional después de que ocurra cierto evento:
+
+- `onLoad`: Ejecuta código después de que el script haya terminado de cargarse.
+- `onReady`: Ejecuta código después de que el script haya terminado de cargarse y cada vez que el componente se monte.
+- `onError`: Ejecuta código si el script falla al cargarse.
+
+
+
+Estos manejadores solo funcionarán cuando `next/script` se importe y use dentro de un [Componente Cliente](/docs/app/getting-started/server-and-client-components) donde `"use client"` esté definido como la primera línea de código:
+
+```tsx filename="app/page.tsx" switcher
+'use client'
+
+import Script from 'next/script'
+
+export default function Page() {
+ return (
+ <>
+