El componente Code: resaltar desde el servidor
Las vallas de Markdown resaltan contenido estático, pero a veces el código llega en una variable, en una respuesta de red o en un fichero importado con ?raw. Para esos casos Astro expone el mismo motor Shiki como un componente invocable, <Code />, importado de astro:components. Renderiza en el servidor con props code, lang, theme y themes, ofrece inline para incrustar código en el texto, y nunca envía JavaScript al cliente.
Una valla de tres backticks es perfecta para el código que escribes a mano dentro de un documento, pero no sirve cuando el código no es estático: cuando viene de una variable, de una respuesta de red, de un fichero que importas o de un bucle que recorre varios ejemplos. Para eso Astro expone el mismo motor Shiki como un componente que puedes invocar: <Code />, importado de astro:components. Es el resaltado convertido en una llamada de servidor —una función de props a HTML— que se resuelve en el build o en el render, con control por instancia del lenguaje, el tema y los transformadores, y sin enviar ni un byte de JavaScript al navegador.
- Importar
<Code />deastro:componentsy renderizar código desde una cadena. - Controlar el resultado con las props
code,lang,themeythemes. - Incrustar código dentro del texto con la prop
inline. - Reconocer que
<Code />se resuelve en el servidor y no hidrata nada en el cliente.
Por qué existe <Code />
En un fichero .astro no puedes escribir una valla de Markdown; y aun en un .mdx, la valla solo sirve para código literal escrito en el sitio. Cuando el código proviene de un valor —una cadena calculada, un fragmento traído de una API, el contenido de un fichero cargado con ?raw— necesitas un resaltado programático, capaz de aceptar ese valor como entrada. Ese es el hueco que llena <Code />: recibe el código en una prop y devuelve el mismo HTML coloreado que produciría una valla, pero desde un valor dinámico.
---
import { Code } from 'astro:components';
---
<Code code={`const respuesta = 42;`} lang="js" />
La prop code es la única obligatoria: la cadena que quieres resaltar. El resto tiene valores por defecto sensatos —lang es 'plaintext', theme es 'github-dark'—, así que el ejemplo de arriba ya produce un bloque JavaScript coloreado. Lo importante es el cambio de naturaleza: donde la valla es declarativa —escribes el código y aparece—, <Code /> es invocable —le pasas el código como dato y lo procesa—, y esa diferencia es la que abre los casos que la valla no puede cubrir.
Los escenarios donde esa diferencia paga son concretos y frecuentes. Recorrer un array de ejemplos y renderizar cada uno con <Code /> dentro de un bucle. Mostrar el contenido de un fichero de configuración real leído del proyecto. Resaltar la respuesta de una API en una página bajo demanda. Pintar el mismo fragmento con dos temas distintos, lado a lado, para comparar paletas. En todos, el código es un dato que fluye por tu componente, no un literal que tecleaste en el sitio, y solo un resaltador invocable puede aceptarlo como entrada.
---
import { Code } from 'astro:components';
const ejemplos = [
{ lang: 'js', code: `const a = 1;` },
{ lang: 'py', code: `a = 1` },
{ lang: 'rs', code: `let a = 1;` },
];
---
{ejemplos.map((e) => <Code code={e.code} lang={e.lang} />)}
Ahí se ve el salto con claridad: un mismo bucle recorre una lista de datos y produce tres bloques coloreados, cada uno en su lenguaje, sin que hayas escrito una sola valla. Eso es imposible con Markdown, que solo conoce literales; y es trivial con <Code />, que trata el código como lo que aquí es, un valor más que se itera.
Props esenciales: code, lang, theme
Cada instancia de <Code /> puede fijar su propio lenguaje y su propio tema, con independencia de la configuración global de markdown.shikiConfig. Eso permite que un bloque concreto use una paleta distinta sin afectar al resto.
---
import { Code } from 'astro:components';
---
<Code code={`SELECT * FROM usuarios;`} lang="sql" theme="nord" />
<Code code={`print("hola")`} lang="python" theme="dracula" />
También acepta themes y defaultColor, exactamente igual que la configuración global, de modo que un componente puede servir su propio par claro/oscuro. Y admite wrap por instancia, para que ese bloque en particular ajuste líneas aunque el resto del sitio no lo haga. La lógica es de anulación local: la configuración global marca el estándar, y cada <Code /> puede desviarse de él cuando tenga una razón.
Hay aquí un matiz que ahorra sorpresas. Cuando no fijas una prop, <Code /> toma el valor por defecto del propio componente —github-dark—, no el que declaraste en markdown.shikiConfig. Es decir, la configuración global gobierna las vallas de Markdown, pero no se propaga sola a los <Code />. Si quieres que un <Code /> siga la paleta global, pásale explícitamente el mismo tema; y si vas a repetir esa elección muchas veces, la solución limpia es escribir un componente propio que envuelva a <Code /> con tus valores por defecto, de modo que la decisión viva en un solo sitio.
---
// src/components/Codigo.astro
import { Code } from 'astro:components';
const { code, lang = 'ts' } = Astro.props;
---
<Code code={code} lang={lang} theme="dracula" wrap />
Con ese envoltorio, el resto del proyecto importa Codigo en lugar de Code y obtiene siempre el mismo tema y el mismo ajuste de línea, sin repetir props en cada uso. Es el patrón habitual para no diseminar decisiones de estilo por cientos de llamadas: centralizas la configuración de tus bloques en un componente, igual que centralizas la de las vallas en shikiConfig.
El caso más valioso es alimentar el componente con un fichero real importado como texto crudo. Astro entiende el sufijo ?raw en una importación y entrega el contenido del fichero como una cadena, que pasas directa a code.
---
import { Code } from 'astro:components';
import fuente from '../ejemplos/demo.ts?raw';
---
<Code code={fuente} lang="ts" />
Ese patrón tiene una virtud enorme para la documentación técnica: el código mostrado es el mismo fichero que compilas y pruebas, no una copia pegada que se desincroniza con el tiempo. Si el ejemplo cambia, el documento cambia con él, porque ambos leen la única fuente de verdad. La documentación deja de mentir por omisión.
El sufijo ?raw es una capacidad del empaquetador, no una lectura en tiempo de ejecución: lee el fichero durante el build y lo incrusta como una cadena dentro del módulo. No hay petición de red ni acceso a disco cuando la página se sirve; para entonces el contenido ya viaja dentro del bundle. Por eso combina tan bien con <Code />: ambos se resuelven antes de que exista el navegador, y el resultado es HTML sin rastro de la operación que lo produjo.
inline y código dentro del texto
Por defecto <Code /> envuelve el resultado en un <pre>, que es un bloque de nivel de párrafo. Pero a veces quieres un fragmento coloreado dentro de una frase —un nombre de función, un comando, un identificador— sin romper el flujo del texto. Para eso está inline: activa un render sin <pre>, produciendo solo un <code> que encaja en línea.
---
import { Code } from 'astro:components';
---
<p>Ejecuta <Code code={`npm run build`} lang="bash" inline /> para compilar el sitio.</p>
Con inline, el comando aparece coloreado en medio de la oración, como una pieza tipográfica más. Es la manera de llevar la precisión de Shiki al texto corrido, no solo a los bloques aislados. La prop meta, por su parte, transporta la cadena que en una valla iría tras el lenguaje —rangos de líneas, banderas— para que los transformadores que la leen funcionen también aquí; la verás en acción en la próxima lección.
code obligatorio
La unica prop imprescindible: la cadena a resaltar, venga de donde venga.
lang y theme por instancia
Cada bloque fija su lenguaje y su tema sin afectar a la configuracion global.
importar con ?raw
Alimenta el componente con un fichero real para que el ejemplo nunca se desincronice.
inline en el texto
Sin pre, solo un code, para incrustar codigo coloreado dentro de una frase.
Servidor siempre, cero cliente
Lo decisivo, y lo que emparenta a <Code /> con todo lo visto en el nivel, es dónde corre. <Code /> se resuelve por completo en el servidor —en el build para páginas estáticas, en la petición para las de bajo demanda— y jamás se hidrata en el cliente. No existe una versión de navegador de este componente, ni una directiva client: que tenga sentido ponerle. Su salida es HTML terminado, y su coste en JavaScript de cliente es exactamente cero.
flowchart LR SRC[cadena en la prop code] --> COMP[componente Code en el servidor] IMP[fichero importado con raw] --> COMP COMP --> OUT[pre o code con HTML coloreado] OUT --> ZERO[cero JavaScript hidratado] style COMP fill:#89b4fa,color:#11111b style ZERO fill:#a6e3a1,color:#11111b
<Code /> es una ventana perfecta a la idea que sostiene todo el modelo de componentes de Astro, y merece mirarla de frente. Estamos acostumbrados a pensar que un componente es una pieza de interfaz que vive en el navegador: tiene estado, reacciona, se vuelve a pintar. Astro invierte esa suposición por defecto. Aquí, un componente es ante todo una función que se ejecuta en el servidor y cuyo valor de retorno es HTML; la interactividad en el cliente es la excepción cara que reservas para las islas que de verdad la necesitan, no la regla. <Code /> es el caso más puro de esa regla, porque no tiene absolutamente nada que hidratar: no guarda estado, no escucha eventos, no cambia tras renderizarse. Es una función pura de sus props a una cadena de marcado, evaluada una vez, colapsada en texto antes de que el navegador exista en la ecuación. Y reconocer esto reordena tu forma de diseñar. La pregunta deja de ser “¿este componente necesita JavaScript?” para volverse “¿qué parte, si es que alguna, de este componente depende de algo que solo el navegador conoce?”. La respuesta, para la inmensa mayoría de lo que construyes —un encabezado, una tarjeta, una lista, un bloque de código— es: nada. Todo su valor está determinado en el servidor, y por tanto todo puede colapsarse a HTML y enviarse sin una gota de código ejecutable. Las islas interactivas no son el material del que está hecho un sitio Astro; son los pocos puntos donde el material se rompe a propósito para admitir estado en el cliente. <Code /> te entrena a ver esa asimetría: la mayoría de tus componentes deberían aspirar a ser como él —resueltos en el servidor, nulos en JavaScript, funciones de datos a marcado— y solo unos pocos, los que manipulan estado vivo frente al usuario, merecen el privilegio y el coste de cruzar al cliente. Interiorizar esa proporción invertida, donde lo estático es la norma y lo interactivo la excepción justificada, es la mitad de aprender a pensar en Astro; la otra mitad es saber dónde trazar la frontera.
- Importa
<Code />deastro:componentsy resalta una cadena conlangy unthemedistinto del global; verifica que solo ese bloque cambia de paleta. - Importa un fichero
.tsreal con?rawy pásalo acode, de modo que el ejemplo mostrado y el compilado sean el mismo fichero. - Usa
inlinepara incrustar un comando coloreado dentro de un párrafo y comprueba que no rompe el flujo del texto. - Inspecciona la red del navegador y confirma que la página no descarga JavaScript para ese resaltado, razonando por qué
<Code />no admite una directivaclient:.