Skip to content

Commit 43907b0

Browse files
committed
xd
1 parent e5df6e6 commit 43907b0

File tree

6 files changed

+518
-67
lines changed

6 files changed

+518
-67
lines changed

app/blog/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { allBlogs } from 'contentlayer/generated'
33
import { genPageMetadata } from 'app/seo'
44
import ListLayout from '@/layouts/ListLayoutWithTags'
55

6-
const POSTS_PER_PAGE = 5
6+
const POSTS_PER_PAGE = 6
77

88
export const metadata = genPageMetadata({ title: 'Blog' })
99

components/BlogCard.tsx

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
'use client'
2+
3+
import Link from 'next/link'
4+
import Tag from '@/components/Tag'
5+
import { formatDate } from 'pliny/utils/formatDate'
6+
import siteMetadata from '@/data/siteMetadata'
7+
import { motion } from 'framer-motion'
8+
import { CoreContent } from 'pliny/utils/contentlayer'
9+
import type { Blog } from 'contentlayer/generated'
10+
11+
interface BlogCardProps {
12+
post: CoreContent<Blog>
13+
}
14+
15+
export default function BlogCard({ post }: BlogCardProps) {
16+
const { title, summary, date, tags, path } = post
17+
18+
return (
19+
<div
20+
className="group relative transform cursor-pointer transition-all duration-500 hover:-rotate-1 hover:scale-105 h-full"
21+
>
22+
<div className="hover:shadow-3xl relative z-10 w-full overflow-hidden rounded-3xl border border-red-500/20 bg-gradient-to-tr from-[#0F0F0F] to-[#0B0B0B] text-white shadow-2xl backdrop-blur-xl duration-700 hover:border-red-500/40 hover:shadow-red-500/10 h-full flex flex-col aspect-square">
23+
{/* Background Effects */}
24+
<div className="absolute inset-0 z-0 overflow-hidden pointer-events-none">
25+
<div className="absolute inset-0 bg-gradient-to-tr from-red-500/5 to-red-400/10 opacity-40 transition-opacity duration-500 group-hover:opacity-60"></div>
26+
<div className="absolute -bottom-20 -left-20 h-48 w-48 transform animate-bounce rounded-full bg-gradient-to-tr from-red-500/10 to-transparent opacity-30 blur-3xl transition-all delay-500 duration-700 group-hover:scale-110 group-hover:opacity-50"></div>
27+
<div className="absolute left-10 top-10 h-16 w-16 animate-ping rounded-full bg-red-500/5 blur-xl"></div>
28+
<div className="absolute bottom-16 right-16 h-12 w-12 animate-ping rounded-full bg-red-500/5 blur-lg delay-1000"></div>
29+
<div className="absolute inset-0 translate-x-full -skew-x-12 transform bg-gradient-to-r from-transparent via-red-500/5 to-transparent transition-transform duration-1000 group-hover:translate-x-[-200%]"></div>
30+
</div>
31+
32+
{/* Content */}
33+
<div className="relative z-10 p-8 flex flex-col h-full">
34+
<div className="mb-4 flex flex-wrap gap-2">
35+
{tags?.map((tag) => (
36+
<Tag key={tag} text={tag} />
37+
))}
38+
</div>
39+
40+
<h2 className="mb-3 text-2xl font-bold leading-tight text-gray-100 group-hover:text-red-500 transition-colors duration-300">
41+
<Link href={`/${path}`} className="focus:outline-none">
42+
<span className="absolute inset-0" aria-hidden="true" />
43+
{title}
44+
</Link>
45+
</h2>
46+
47+
<div className="mb-4 flex items-center text-sm text-gray-400">
48+
<time dateTime={date}>{formatDate(date, siteMetadata.locale)}</time>
49+
</div>
50+
51+
<p className="mb-6 text-gray-300 line-clamp-4 flex-grow text-sm sm:text-base">
52+
{summary}
53+
</p>
54+
55+
<div className="mt-auto pt-4 flex items-center text-sm font-medium text-red-500 transition-colors duration-300 group-hover:text-red-400">
56+
Leer Más
57+
<svg className="ml-2 h-4 w-4 transition-transform duration-300 group-hover:translate-x-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
58+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M14 5l7 7m0 0l-7 7m7-7H3" />
59+
</svg>
60+
</div>
61+
62+
{/* Decorative corner accents */}
63+
<div className="absolute left-0 top-0 h-20 w-20 rounded-br-3xl bg-gradient-to-br from-red-500/10 to-transparent opacity-0 transition-opacity duration-500 group-hover:opacity-100 pointer-events-none"></div>
64+
<div className="absolute bottom-0 right-0 h-20 w-20 rounded-tl-3xl bg-gradient-to-tl from-red-500/10 to-transparent opacity-0 transition-opacity duration-500 group-hover:opacity-100 pointer-events-none"></div>
65+
</div>
66+
</div>
67+
</div>
68+
)
69+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
---
2+
title: 'Client Components vs Server Components en Next.js: ¿Cuál usar?'
3+
date: '2026-02-18'
4+
tags: ['nextjs', 'react', 'performance']
5+
draft: false
6+
summary: 'Entiende la diferencia fundamental entre Client y Server Components en el App Router de Next.js. Aprende cuándo usar "use client" y cómo optimizar tu aplicación.'
7+
---
8+
9+
<TOCInline toc={props.toc} asDisclosure />
10+
11+
## El Nuevo Paradigma de React
12+
13+
Con la llegada de **React Server Components (RSC)** a través del App Router de Next.js, la forma en que pensamos sobre nuestras aplicaciones ha cambiado. Ya no se trata de "SSG vs SSR", sino de dónde se renderiza cada componente individualmente.
14+
15+
Por defecto, en Next.js (App Router), **todos los componentes son Server Components**. Esto significa que se renderizan exclusivamente en el servidor y envían HTML puro al cliente, sin hidratación de JavaScript para esa parte.
16+
17+
## Server Components: Tu Nuevo Estándar
18+
19+
Los Server Components son ideales para todo lo que no requiere interactividad inmediata del usuario.
20+
21+
### Ventajas
22+
23+
1. **Cero Bundle Size**: Las librerías que uses aquí (ej. formateadores de fecha, parseadores de markdown) no se envían al cliente.
24+
2. **Acceso Directo al Backend**: Puedes leer la base de datos o sistema de archivos directamente en el componente.
25+
3. **Seguridad**: El código sensible (tokens, llaves de API) nunca sale del servidor.
26+
27+
### ¿Cuándo usarlos?
28+
29+
- Obtención de datos (Data Fetching).
30+
- Layouts estáticos o estructura de la página.
31+
- Renderizado de contenido Markdown.
32+
- SEO y metadatos.
33+
34+
```tsx
35+
// app/page.tsx (Server Component por defecto)
36+
import { db } from '@/lib/db'
37+
38+
export default async function Page() {
39+
const posts = await db.post.findMany() // ¡Directo a la BD!
40+
41+
return (
42+
<main>
43+
<h1>Mis Posts</h1>
44+
{posts.map((post) => (
45+
<p key={post.id}>{post.title}</p>
46+
))}
47+
</main>
48+
)
49+
}
50+
```
51+
52+
## Client Components: Interactividad
53+
54+
Los Client Components son los componentes de React "de toda la vida". Se renderizan en el servidor (HTML inicial) y luego se _hidratan_ en el cliente para volverse interactivos.
55+
56+
Para convertir un componente en Client Component, debes añadir la directiva `'use client'` al principio del archivo.
57+
58+
### ¿Cuándo usarlos?
59+
60+
- Eventos del usuario (`onClick`, `onChange`).
61+
- Hooks de React (`useState`, `useEffect`, `useReducer`).
62+
- APIs del navegador (`localStorage`, `window`, `geolocation`).
63+
- Animaciones (Framer Motion, GSAP).
64+
65+
```tsx
66+
'use client'
67+
68+
import { useState } from 'react'
69+
70+
export default function Counter() {
71+
const [count, setCount] = useState(0)
72+
73+
return <button onClick={() => setCount(count + 1)}>Contador: {count}</button>
74+
}
75+
```
76+
77+
## El Patrón de "Hojas" (Leaf Pattern)
78+
79+
Para optimizar el rendimiento, intenta empujar los Client Components lo más abajo posible en el árbol de componentes (hacia las "hojas").
80+
81+
**Mal patrón:** Hacer que toda la página sea `'use client'` solo porque un botón necesita interactividad.
82+
83+
**Buen patrón:** Mantener la página como Server Component y solo importar el botón interactivo.
84+
85+
```tsx
86+
// app/page.tsx (Server)
87+
import ClientButton from './ClientButton'
88+
89+
export default function Page() {
90+
return (
91+
<div>
92+
<h1>Título Estático (Server)</h1>
93+
<p>Contenido pesado que no se envía al cliente (Server)</p>
94+
<ClientButton /> {/* Solo esto es cliente */}
95+
</div>
96+
)
97+
}
98+
```
99+
100+
## Conclusión
101+
102+
No veas esto como una elección binaria para toda tu app. La magia de Next.js está en mezclar ambos. Usa **Server Components** por defecto para rendimiento y SEO, y salpica **Client Components** solo donde necesites interactividad. ¡Tu bundle size te lo agradecerá!
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
---
2+
title: 'Next.js 14 Server Actions: Guía Completa para Olvidar las API Routes'
3+
date: '2026-02-17'
4+
tags: ['nextjs', 'react', 'javascript']
5+
draft: false
6+
summary: 'Descubre cómo simplificar tu backend en Next.js 14 eliminando las API Routes tradicionales y adoptando Server Actions para un código más limpio y eficiente.'
7+
---
8+
9+
<TOCInline toc={props.toc} asDisclosure />
10+
11+
## Introducción
12+
13+
Con la llegada de **Next.js 14** y el App Router estable, la forma en que manejamos las mutaciones de datos en React ha cambiado radicalmente. Si vienes de versiones anteriores o de otros frameworks, probablemente estés acostumbrado a crear endpoints en `/pages/api` o `/app/api` para manejar formularios y actualizaciones de base de datos.
14+
15+
Las **Server Actions** llegan para cambiar esto. Nos permiten ejecutar código del lado del servidor directamente desde nuestros componentes, eliminando la necesidad de crear una API intermedia para muchas tareas comunes.
16+
17+
En esta guía, aprenderás qué son, por qué usarlas y cómo migrar tus viejas API routes a este nuevo paradigma.
18+
19+
## ¿Qué son las Server Actions?
20+
21+
Las Server Actions son **funciones asíncronas** que se ejecutan en el servidor pero que pueden ser invocadas desde el cliente (o desde otros componentes del servidor). Se basan en las React Actions y están perfectamente integradas con el ciclo de vida de renderizado de Next.js.
22+
23+
### Ventajas Clave
24+
25+
1. **Menos Boilerplate**: No necesitas definir rutas, métodos HTTP (GET/POST), ni headers manualmente.
26+
2. **Type Safety (Tipado)**: Al ser funciones directas, TypeScript infiere los tipos de entrada y salida automáticamente entre cliente y servidor.
27+
3. **Progressive Enhancement**: Funcionan incluso si JavaScript está deshabilitado en el navegador (cuando se usan con formularios HTML estándar).
28+
4. **Revalidación Integrada**: Puedes actualizar la caché de tus datos (`revalidatePath`) en la misma función, haciendo que la UI se actualice instantáneamente.
29+
30+
## Ejemplo Práctico: Creando un Todo List
31+
32+
Vamos a ver la diferencia entre el método "viejo" y el "nuevo".
33+
34+
### La Forma Antigua (API Routes)
35+
36+
Antes, para añadir un "Todo", necesitabas dos archivos.
37+
38+
**1. El Endpoint (`app/api/todo/route.ts`)**
39+
40+
```ts
41+
import { NextResponse } from 'next/server'
42+
import { db } from '@/lib/db'
43+
44+
export async function POST(request: Request) {
45+
const data = await request.json()
46+
await db.todo.create({ data })
47+
return NextResponse.json({ success: true })
48+
}
49+
```
50+
51+
**2. El Componente Cliente (`components/AddTodo.tsx`)**
52+
53+
```tsx
54+
'use client'
55+
56+
export default function AddTodo() {
57+
async function handleSubmit(e) {
58+
e.preventDefault()
59+
await fetch('/api/todo', {
60+
method: 'POST',
61+
body: JSON.stringify({ text: 'Nueva tarea' }),
62+
})
63+
// Y luego recargar la página o manejar estado global...
64+
}
65+
66+
return <form onSubmit={handleSubmit}>...</form>
67+
}
68+
```
69+
70+
Es mucho código para algo tan simple, y perdemos el tipado entre el `fetch` y el endpoint.
71+
72+
### La Forma Nueva (Server Actions)
73+
74+
Con Server Actions, todo puede vivir junto (o en un archivo separado de acciones) y es mucho más directo.
75+
76+
**1. La Acción (`actions.ts`)**
77+
78+
```ts
79+
'use server'
80+
81+
import { db } from '@/lib/db'
82+
import { revalidatePath } from 'next/cache'
83+
84+
export async function createTodo(formData: FormData) {
85+
const text = formData.get('text') as string
86+
87+
if (!text) return
88+
89+
await db.todo.create({ data: { text } })
90+
91+
// ¡Magia! Esto actualiza la UI automáticamente
92+
revalidatePath('/todos')
93+
}
94+
```
95+
96+
**2. El Componente (`components/AddTodo.tsx`)**
97+
98+
```tsx
99+
import { createTodo } from '@/actions'
100+
101+
export default function AddTodo() {
102+
return (
103+
<form action={createTodo}>
104+
<input name="text" type="text" required className="border p-2" />
105+
<button type="submit">Agregar</button>
106+
</form>
107+
)
108+
}
109+
```
110+
111+
¡Eso es todo! Observa que:
112+
113+
- No hay `fetch`.
114+
- No hay `useState` para el input (el formulario lo maneja).
115+
- `revalidatePath` se encarga de refrescar los datos en pantalla sin que tengas que hacer nada en el cliente.
116+
117+
## Manejo de Estados de Carga (useFormStatus)
118+
119+
Una pregunta común es: "¿Cómo muestro un spinner si no tengo un estado `isLoading`?"
120+
121+
React nos da el hook `useFormStatus` para esto. Solo funciona dentro de un componente que esté renderizado _dentro_ del formulario, por lo que solemos extraer el botón a su propio componente.
122+
123+
```tsx
124+
'use client'
125+
126+
import { useFormStatus } from 'react-dom'
127+
128+
function SubmitButton() {
129+
const { pending } = useFormStatus()
130+
131+
return (
132+
<button disabled={pending} type="submit" className="rounded bg-blue-500 p-2 text-white">
133+
{pending ? 'Guardando...' : 'Agregar Tarea'}
134+
</button>
135+
)
136+
}
137+
```
138+
139+
## Validaciones con Zod
140+
141+
Para aplicaciones reales, no debes confiar ciegamente en `formData`. Lo ideal es usar **Zod** para validar los datos en el servidor antes de procesarlos.
142+
143+
```ts
144+
'use server'
145+
146+
import { z } from 'zod'
147+
148+
const schema = z.object({
149+
text: z.string().min(3, { message: 'Mínimo 3 caracteres' }),
150+
})
151+
152+
export async function createTodo(formData: FormData) {
153+
const parsed = schema.safeParse({
154+
text: formData.get('text'),
155+
})
156+
157+
if (!parsed.success) {
158+
return { error: parsed.error.flatten().fieldErrors }
159+
}
160+
161+
// Procesar datos...
162+
}
163+
```
164+
165+
## Conclusión
166+
167+
Las **Server Actions** no solo son "otra feature más", son un cambio de paradigma que simplifica enormemente el desarrollo full-stack con Next.js. Al reducir la separación artificial entre cliente y servidor, escribimos menos código, cometemos menos errores de tipo y entregamos aplicaciones más rápidas.
168+
169+
Si todavía estás escribiendo `POST` handlers en 2026 para mutaciones simples, ¡es hora de refactorizar!
170+
171+
---
172+
173+
_¿Te ha gustado este artículo? Revisa otros posts sobre [Next.js](/tags/next) y [React](/tags/react) en el blog._

0 commit comments

Comments
 (0)