wandres.dev
MDX A FONDO · componentes y expresiones

Expresiones y exportaciones en MDX

El cuerpo de un fichero MDX como código ejecutable: insertar expresiones JavaScript entre llaves, definir variables y valores derivados con export const, la relación entre el frontmatter YAML de datos y las exportaciones computadas, y el límite que separa una expresión válida de una sentencia que MDX no admite.

⏱ 14 min

Si en la lección anterior el documento aprendió a contener componentes, en esta aprende a calcular. El cuerpo de un .mdx no es texto inerte: es el cuerpo de un módulo de JavaScript, donde una expresión entre llaves se evalúa y su resultado se inserta en la prosa, donde export const define valores, y donde el frontmatter de datos convive con la computación. Entender MDX a fondo es dejar de leer el fichero como un texto con adornos y empezar a leerlo como el programa que en realidad es.

🎯 Al terminar esta lección sabrás
  • Insertar expresiones JavaScript en el cuerpo con llaves y comprender qué se evalúa y qué no.
  • Definir variables y valores derivados con export const en la cabecera del documento.
  • Relacionar el frontmatter YAML —datos estáticos— con las exportaciones computadas de JavaScript.
  • Reconocer el límite entre una expresión, que MDX admite, y una sentencia, que rechaza.

Expresiones en el cuerpo con llaves

La forma más directa de meter cómputo en un documento es la expresión entre llaves. Escribes { seguido de cualquier expresión JavaScript y }, y MDX la evalúa e inserta su resultado en ese punto del texto. Es la misma sintaxis de las plantillas de Astro, ahora disponible en medio de la prosa.

---
title: Estado del proyecto
---

# {title.toUpperCase()}

Documento generado el {new Date().toLocaleDateString('es-ES')}.

Quedan {30 - 12} tareas por cerrar de un total de {30}.

La clave está en la palabra expresión: entre las llaves solo cabe algo que produzca un valor. Una operación aritmética, una llamada a método, un acceso a una propiedad, una plantilla de cadena, un ternario: todo eso es expresión y funciona. Lo que no cabe es una sentencia —un if, un for, una declaración— porque una sentencia no devuelve nada que insertar. Esa restricción, que parece limitante, es la misma que rige el JSX y la que te empuja a los patrones funcionales de siempre.

⚠️
Las expresiones se evalúan al renderizar, no en el navegador

Una expresión de MDX corre en el servidor durante el renderizado —en el build si generas un sitio estático, o por petición si usas SSR—, nunca de forma reactiva en el cliente. Un {Date.now()} queda congelado en el instante en que se generó la página, no cambia solo con el paso del tiempo, y un cálculo no se recomputa cuando el usuario interactúa. Para valores que deban vivir y reaccionar en el navegador, el camino no es la expresión, sino la isla: un componente de framework hidratado con una directiva client. Confundir ambos es esperar dinamismo de un valor que se resolvió una vez y se quedó fijo.

Variables y valores con export const

Para no repetir un cálculo o para dar nombre a un dato, defines variables en la cabecera del documento con export const. Estas declaraciones viven en el ámbito del módulo, así que cualquier expresión del cuerpo puede usarlas, y además —por ser exportadas— quedan disponibles para quien importe el fichero.

---
title: Informe trimestral
---
export const ventas = 128;
export const meta = 150;
export const cumplido = Math.round((ventas / meta) * 100);

# {title}

Se alcanzaron {ventas} de {meta} ventas: un {cumplido} por ciento de la meta.

Nada te obliga a exportarlas: un const a secas también funciona como variable local del módulo. Pero export const es el idioma canónico en MDX por una razón doble: expresa la intención de que ese valor forme parte del contrato del documento, y lo hace accesible desde fuera. Un valor derivado como cumplido muestra el poder del enfoque: se computa una vez, a partir de otros dos, y se reutiliza en la prosa sin que el redactor tenga que recalcularlo a mano ni arriesgarse a que las cifras se desincronicen.

Ese “accesible desde fuera” es literal y merece verse. Como el .mdx compila a un módulo, otro fichero puede importarlo y leer sus valores exportados como leería los de cualquier librería. Junto a esos valores, el módulo expone el componente Content para renderizar el cuerpo y el objeto del frontmatter.

---
import { Content, ventas, cumplido } from '../content/informe.mdx';
---
<p>El informe declara {ventas} ventas y un {cumplido} por ciento cumplido.</p>
<Content />

Un documento deja así de ser un pozo cerrado del que solo sale HTML: se vuelve una fuente de datos consultable por su propia página. La misma cifra que el texto muestra en su cuerpo puede alimentar una tarjeta de resumen en la plantilla que lo envuelve, sin duplicarla, porque ambas leen el mismo export const.

📝
Frontmatter y exportaciones llegan por caminos distintos al importar

Cuando importas un .mdx como módulo, el frontmatter YAML llega agrupado bajo frontmatter y los export const llegan como exportaciones con nombre, cada una por su identificador. Son dos espacios separados: frontmatter.title para lo declarado en YAML, ventas a secas para lo exportado en el cuerpo. Esta separación es coherente con su naturaleza —datos declarativos frente a valores de código— y conviene tenerla presente para no buscar un campo de frontmatter entre las exportaciones ni al revés.

💡
Computa una vez, usa muchas

Cuando un mismo valor aparece varias veces en un documento —un año, un total, un porcentaje— defínelo una sola vez con export const y refiérete a él por su nombre en cada aparición. Así, cambiar el dato es editar una línea, no cazar todas sus copias por el texto, y el compilador te avisa si escribes mal el nombre de la variable. Es el principio de una sola fuente de verdad aplicado dentro de un documento.

Frontmatter YAML frente a valores exportados

MDX ofrece dos maneras de asociar datos a un documento, y conviene no confundirlas. El frontmatter YAML —el bloque entre guiones— es para datos estáticos y declarativos: cadenas, números, fechas, listas literales que describen el documento. Las exportaciones con export const son para valores de JavaScript, incluidos los que se computan a partir de imports o de otras variables. YAML no ejecuta; export const sí.

🗂️

Frontmatter YAML

Datos planos y declarativos —título, fecha, etiquetas— que describen el documento. No admite lógica ni referencias; es texto estructurado que Astro lee sin ejecutar.

🧮

export const

Valores de JavaScript, computados si hace falta a partir de imports u otras variables. Ejecuta en el módulo y se exporta hacia quien consuma el fichero.

La frontera práctica es de naturaleza, no de gusto. Un title fijo va en el frontmatter, porque es un dato que no necesita calcularse. Un total que sale de sumar una lista importada va en un export const, porque YAML no sabe sumar. Y ambos coexisten sin estorbarse: el frontmatter describe, las exportaciones computan, y el cuerpo del documento consume libremente de las dos fuentes con la misma sintaxis de llaves.

flowchart TD
YAML[frontmatter YAML] --> DATOS[datos estaticos declarativos]
IMP[import de otros modulos] --> EXP[export const computa]
VAR[otras variables] --> EXP
EXP --> VAL[valores derivados]
BODY[cuerpo del documento] --> LLAVES[expresiones con llaves]
DATOS --> LLAVES
VAL --> LLAVES
LLAVES --> OUT[HTML renderizado]
style EXP fill:#89b4fa,color:#11111b
style OUT fill:#a6e3a1,color:#11111b

El límite: expresiones, no sentencias

El error más común al empezar con MDX es querer escribir lógica de control entre las llaves. No se puede poner un if ni un for dentro de { }, porque son sentencias y no devuelven valor. La solución es la misma que en JSX: traduces la condición a un ternario o a un &&, y el bucle a un .map() que devuelve un array de nodos.

---
title: Lista
---
export const tareas = ['diseño', 'código', 'pruebas'];
export const urgente = true;

{urgente && <strong>Hay trabajo urgente.</strong>}

<ul>
  {tareas.map((t) => <li>{t}</li>)}
</ul>

Ese giro no es una incomodidad que sortear, sino la forma en que MDX te empuja hacia un estilo declarativo. En lugar de decir cómo recorrer la lista con un bucle imperativo, describes qué quieres —un li por cada tarea— y dejas que .map() lo produzca. La ausencia de sentencias no te quita expresividad; te obliga a expresarla como transformación de datos, que es justo el idioma en el que las interfaces se piensan mejor.

No es casualidad que estas reglas coincidan al milímetro con las de las expresiones en una plantilla .astro: son el mismo motor. Lo que aprendiste sobre {expresion}, ternarios y .map() en el template de Astro se transfiere entero a MDX, porque en ambos casos el compilador espera una expresión que produzca un nodo. Esa unidad conceptual es deliberada y ahorra memoria: no hay dos sistemas de plantillas que dominar, hay uno solo que reaparece —en un .astro, dentro de la plantilla; en un .mdx, dentro de la prosa—.

Un documento MDX es un módulo, y eso lo cambia todo

La tentación al aprender MDX es verlo como Markdown con un par de trucos: unas llaves para meter una fecha, un export const para un total. Esa lectura se queda corta y oculta lo esencial. Un fichero .mdx se compila a un módulo de JavaScript, y todo lo demás se deriva de ese hecho con una coherencia implacable. Las expresiones entre llaves funcionan porque el cuerpo es código que se ejecuta. Los imports funcionan porque un módulo importa. Los export const funcionan porque un módulo exporta, y por eso otro fichero puede importar tu documento y leer sus valores como leería los de cualquier librería. La prohibición de sentencias dentro de las llaves no es un capricho del parser, sino la consecuencia de que ahí se espera una expresión que produzca un nodo, igual que en JSX. Cuando aceptas que el documento es un módulo, dejas de memorizar reglas sueltas y empiezas a deducirlas de un solo principio. Esta identidad —documento igual a módulo— tiene consecuencias que exceden la comodidad de teclear. Significa que tu contenido participa del grafo de dependencias del proyecto: puede importar datos de un fichero compartido, y si ese fichero cambia, tu documento cambia con él, sin copiar nada. Significa que un valor computado en la cabecera es una única fuente de verdad de la que el resto del texto se alimenta, de modo que las cifras de un documento no pueden contradecirse entre sí. Y significa que la vieja distinción entre “datos” —el frontmatter— y “lógica” —el cuerpo— no es una separación de mundos, sino dos secciones del mismo programa, una declarativa y otra ejecutable, que colaboran. Interiorizar esto es el salto que separa usar MDX de dominarlo. Quien lo ve como Markdown adornado escribe expresiones por prueba y error y se estrella contra cada límite sin entender por qué. Quien lo ve como un módulo que casualmente se escribe casi todo en prosa razona sobre él como razona sobre cualquier código: con ámbitos, con dependencias, con valores que fluyen. La segunda mirada no solo evita errores; abre un uso del contenido —dinámico, conectado, derivado— que la primera ni siquiera imagina.

⚔️ Trata tu documento como el módulo que es
  1. En un .mdx, define tres export const donde el tercero se compute a partir de los dos primeros, y muéstralo en la prosa con expresiones entre llaves.
  2. Intenta escribir un if dentro de unas llaves, observa el error de compilación, y reescríbelo como ternario y como && hasta que funcione.
  3. Distribuye la misma información entre frontmatter YAML y export const según su naturaleza —dato estático o valor computado— y justifica cada colocación.
  4. Importa un array desde otro fichero del proyecto, recórrelo con .map() en el cuerpo del documento, y razona qué gana tu contenido al participar del grafo de dependencias.