Playground / Experimento
Notion CMS
Integración técnica detrás de /now. Un Server Component lee directamente la Notion API con ISR. Sin SDK externo, sin MDX intermediario.
Decisiones tomadas: marzo 2026 · Sesión de contenidos.
- Página
- Now
- ID
- 0aa0a698a15f4842898a02f5ca517d84
- Bloques
- 9
- Última edición
- 20/4/2026, 5:41:00
- Sincronizado
- 30/5/2026, 13:52:35
- Revalida cada
- 3600s (1h)
Framework — Burner List de Jake Knapp
/now no es un CV actualizado ni una lista de tareas. Es una foto honesta del estado presente: lo que tiene el foco, lo que está en segundo plano, y el ruido que hay que gestionar. La estructura viene de The Burner List de Jake Knapp.
Una hoja dividida en dos columnas. Izquierda = fuego delantero. Derecha = fuego trasero.
Front burner — Un único proyecto. El más importante. Solo uno. Las tareas concretas que lo mueven en los próximos días. El espacio en blanco restante es enfoque visible.
Back burner — El segundo proyecto más importante. Consciente pero no protagonista.
Kitchen sink — Todo lo demás sin categorizar. El fregadero donde va el resto.
La regla más importante: la Burner List es desechable. Se rehace cuando cambia el front burner. El acto de rehacer te obliga a decidir qué sigue siendo importante y qué ya no.
Building in public
/now es el canal principal de la estrategia de construir en público. Cada vez que el front burner cambia, hay una historia que contar:
- Pablo actualiza /now en Notion
- Ese estado se convierte en contenido para redes
- El visitante que llega referenciado ve la prueba en vivo de que el sitio está en construcción
- Conecta con el posicionamiento: aprende haciendo, proceso visible, no portfolio muerto
Planteamiento
/now necesitaba actualizarse desde cualquier sitio, sin tocar el código. La solución más simple: Notion como CMS. Este experimento documenta cómo se construyó esa integración en fases, qué decisiones se tomaron en cada momento y por qué.
Qué es /now
El "frontdesk" de pablobellver.com. La página que responde: ¿qué está pasando ahora mismo? No es un CV actualizado. No es una lista de tareas. Es una foto del presente — lo que tiene el foco, lo que está en segundo plano, y el ruido que hay que gestionar. Pública porque esa transparencia es parte del proceso.
Fuente de datos
Página: Now — ID 0aa0a698-a15f-4842-898a-02f5ca517d84
Una página simple a nivel workspace en Notion (no hija de ningún otro documento, no wiki, no database). Contiene la Burner List directamente como bloques: headings + callouts. Pablo la edita libremente. Sirve como CMS único de /now.
Flujo buscado
Sería el flujo ideal pero no tiene porque ser el inicial para empezar con el experimento.
Pablo edita la página de Notion
↓
Webhook de Notion detecta el cambio
↓
Script convierte el contenido a content/now.mdx
↓
Vercel detecta el cambio en el repo y lanza rebuild
↓
/now actualizado en producciónVamos a realizar varias fases. Empezaremos por una sincronización cada cierto tiempo. Luego añadiremos script manual para leer la página de Notion vía API y escribe el content/now.mdx → commit + push → Vercel rebuild. Sin infraestructura de webhooks, sin complejidad extra.
Por último y si la frecuencia de actualización lo justifica puedo añadir el webhook después.
V1 — ISR con Notion y Vercel
Estado: ✅ Implementada
Lectura directa desde Notion vía API con ISR — Incremental Static Regeneration (revalidación cada hora). Sin script manual, sin MDX intermediario. El Server Component lee la página en build time y Next.js la regenera automáticamente.
Arquitectura
Notion API → lib/notion.ts (fetch + parse) → Server Component → /nowVariables de entorno necesarias:
NOTION_TOKEN_NOW=ntn_... # Integration token
NOTION_NOW_PAGE_ID=0aa0a698-... # ID de la página NowLas variables van en Project Settings de Vercel, no en Team Settings. Las de equipo no se inyectan en los builds de proyectos individuales.
El flujo
Pablo edita la página de Notion
↓
Notion API devuelve los bloques
↓
fetchBlocks() los lee con fetch nativo (ISR: revalidate 3600s)
↓
Next.js sirve HTML pre-renderizado
↓
/now actualizado en producción (máx. 1h de latencia)El código
// lib/notion.ts — fetch recursivo (bloques e hijos)
async function fetchBlocks(blockId: string, revalidate: number) {
const res = await fetch(
`https://api.notion.com/v1/blocks/${blockId}/children`,
{
headers: { Authorization: `Bearer ${process.env.NOTION_TOKEN_NOW}` },
next: { revalidate }, // ← ISR automático
}
)
const data = await res.json()
const blocks = data.results ?? []
for (const block of blocks) {
if (block.has_children) {
block.children = await fetchBlocks(block.id, revalidate)
}
}
return blocks
}
// app/now/page.tsx
export const revalidate = 3600
export default async function NowPage() {
const data = await getNowPage() // server-side, cacheado
return <BlocksRenderer groups={groupBlocks(data.blocks)} />
}Incidencias resueltas
Callouts sin contenido interior
La Notion API devuelve callouts con has_children: true pero fetchBlocks solo leía el primer nivel. El renderer pintaba el rich_text del bloque raíz —vacío— ignorando los bloques hijos. Fix: fetchBlocks ahora es recursiva. Si un bloque tiene has_children: true, fetchea sus hijos y los asigna a block.children.
H3 dentro de callouts
Los callouts con un heading_3 como primer hijo no lo mostraban como título — el H3 quedaba perdido en el cuerpo. Fix: si el primer hijo es heading_3, se extrae y se renderiza como título del callout. El resto de hijos pasa a BlocksRenderer normalmente.
Lecciones aprendidas
- Las variables de entorno tienen scope y no es obvio. Vercel distingue entre variables de equipo y variables de proyecto. El error no te dice dónde está el problema — simplemente la variable no existe. Cuesta tiempo real hasta que lo ves.
- Nombrar bien desde el principio tiene coste cero.
NOTION_TOKEN_NOWen lugar deNOTION_TOKENgenérico. Evita ambigüedad cuando haya más integraciones y hace explícita la separación entre servicios. - Las APIs tienen niveles de profundidad que no se ven hasta que algo falla. La Notion API devuelve bloques con
has_children: truepero no incluye los hijos en la misma llamada. El bug no es de lógica sino de arquitectura de la llamada. - Verificar en producción antes de dar algo por hecho. El token funcionaba desde el paso 1 pero no había forma de saberlo hasta que el error cambió. Cada error resuelto revela el siguiente. El flujo correcto: configurar → desplegar → verificar → siguiente paso.
- La complejidad técnica se prioriza por frecuencia de uso, no por elegancia. ISR cada hora cubre el caso de uso real de /now. Añadir webhooks desde el día uno habría sido sobreingenería. La decisión correcta: hacer funcionar lo mínimo necesario, documentar la evolución posible, revisarlo cuando el uso lo justifique.
→ V1 funcionó. La integración estaba en producción, el contenido se leía desde Notion y la web se actualizaba automáticamente cada hora. El siguiente problema: editar en Notion y tener que esperar hasta 60 minutos para ver el cambio publicado dejaba margen claro de mejora.
Next step — Publicar cambios de forma inmediata
ISR revalida automáticamente cada hora. Para el uso habitual de /now es suficiente. Pero cuando se necesita publicar un cambio de forma inmediata hay dos caminos posibles.
| Opción A | Opción B | |
|---|---|---|
| Mecanismo | Deploy Hook + script npm | Webhook de Notion |
| Automatización | Manual — tú lanzas el script | Automática al guardar en Notion |
| Complejidad | Baja | Alta |
| Versión | Versión 2.0 | Versión 3.0 |
→ Se elige Opción A para Versión 2. Coste de implementación mínimo, valor inmediato. No requiere infraestructura adicional ni configuración en el lado de Notion. La Opción B se reserva para cuando el volumen de cambios justifique la automatización completa.
V2 — Publicación bajo demanda
Estado: ✅ Implementada
Deploy Hook de Vercel + script npm. Editas en Notion, ejecutas un comando, Vercel lanza un redeploy inmediato sin esperar el ciclo de ISR.
npm run sync-now --workspace=apps/webImplementación:
apps/web/package.json→ scriptsync-nowque hace POST a$VERCEL_DEPLOY_HOOK_NOWapps/web/.env.local→ variableVERCEL_DEPLOY_HOOK_NOWcon la URL del hook (nunca va al repo).vscode/tasks.json→ task "Publicar /now" para lanzar conCmd+Shift+Pdesde VS Code sin abrir el terminal
Crear el Deploy Hook en Vercel: Proyecto → Settings → Git → Deploy Hooks → nombre sync-now, rama main.
Mejoras visuales (28 mar 2026)
Casos de uso resueltos
- Front/Back burners: Título destacado a la izquierda, acción visible sin scroll
- Kitchen Sink: Sin título = bloque único, rendering correcto
- Mobile: Contenido legible en pantallas pequeñas (1 columna)
- Ultrawide: Contenido no se ve aislado (proporciones optimizadas)
- Temas: Mantiene consistencia visual en todos los temas
Soporte de nuevos tipos de bloque
Nuevos renderers añadidos en lib/notion.ts y app/now/page.tsx.
- Toggle — renderizado con el elemento nativo
<details>/<summary>. Sin JavaScript adicional — el navegador gestiona el estado abierto/cerrado. Los hijos se renderizan conBlocksRenderer, lo que permite anidamiento ilimitado. - Child page — cuando la página de Notion tiene páginas hijas enlazadas como bloque, se renderiza como referencia no navegable: icono + título.
Callout responsive
Se modifica el styles/now.css:
- El callout pasó de
display: flexadisplay: gridpara permitir layout de dos columnas sin romper en mobile. - Grid se adapta según viewport (5 media queries desde mobile → ultrawide)
- Nuevas clases:
.notion-callout-titley.notion-callout-content. - Estilos para toggle y child_page incluidos.
V3 — Actualización con webhook
Estado: 🚧 Pendiente
Notion detecta el cambio al guardar → llama al Deploy Hook automáticamente → Vercel redeploy. Sin intervención manual. Más complejidad de configuración que V2 pero publicación completamente transparente.