Prism como alternativa: cliente frente a servidor y rendimiento
Astro también soporta Prism como resaltador: markdown.syntaxHighlight puede valer 'prism' y existe el componente <Prism /> de @astrojs/prism, con tokens por clase y una hoja de estilos externa en lugar de estilos en línea. Aclarar el intercambio Shiki frente a Prism, y el eje ortogonal y más profundo de resaltar en el servidor frente a hacerlo en el cliente, con su modelo de coste, cierra el nivel con criterio de arquitectura.
Shiki es el resaltador por defecto de Astro, pero no el único. El framework también soporta Prism, y ofrece el componente <Prism /> de @astrojs/prism para quien lo prefiera. Elegir entre uno y otro es un intercambio concreto —precisión y estilos en línea frente a HTML más ligero y una hoja de estilos compartida—, pero conviene no confundirlo con una decisión mucho más honda que lo cruza en perpendicular: la de resaltar en el servidor o en el cliente. Esa segunda pregunta no va de qué librería usas, sino de en qué etapa del ciclo de vida corre el cálculo, y su respuesta se decide con un modelo de coste que vale para el resaltado y para casi todo lo demás.
- Activar Prism con
syntaxHighlight: 'prism'y usar el componente<Prism />. - Contrastar Shiki y Prism en precisión, peso del HTML y estilos.
- Distinguir el eje Shiki/Prism del eje servidor/cliente, que es ortogonal.
- Decidir dónde resaltar según cuándo se conoce el código y cuánto se reutiliza.
<Prism /> y el resaltado con Prism
Cambiar el resaltador de Markdown a Prism es una línea en la configuración: syntaxHighlight: 'prism'. A diferencia de Shiki, Prism no escribe colores en línea; emite tokens con clases —.token.keyword, .token.string—, de modo que los colores viven en una hoja de estilos aparte que tú debes incluir. Sin ese CSS, el código sale con la estructura de tokens marcada pero sin color.
// astro.config.mjs
import { defineConfig } from 'astro/config';
export default defineConfig({
markdown: {
syntaxHighlight: 'prism',
},
});
Con ese ajuste, todas las vallas de Markdown pasan a resolverse con Prism en lugar de Shiki. El componente <Prism />, por su parte, funciona con independencia de esa opción global, igual que <Code /> lo hacía con Shiki: uno gobierna el Markdown, el otro se invoca donde lo necesites.
---
import { Prism } from '@astrojs/prism';
---
<Prism lang="js" code={`const x = 1;`} />
Para que ese resaltado tenga color hay que incluir una hoja de estilos de tema de Prism —una de las muchas que su ecosistema ofrece— en el head de la página o en un layout compartido.
---
// en un layout compartido
---
<link rel="stylesheet" href="/estilos/prism-tomorrow.css" />
Prism es un proyecto veterano y omnipresente: durante años fue el resaltador por defecto de incontables sitios, y su catálogo de temas y complementos es enorme. Esa madurez es parte de su atractivo, junto con lo liviano de su salida. La contrapartida es esa dependencia externa: el color ya no viaja con el bloque, sino en una hoja aparte que hay que cargar, mantener y no olvidar, justo la pieza que Shiki elimina al escribir el color en línea.
Prism resuelve sus gramáticas con expresiones regulares, no con gramáticas TextMate, lo que lo hace más ligero pero menos preciso: en lenguajes con sintaxis incrustada o construcciones ambiguas, acierta menos que Shiki. Su HTML, en cambio, es más compacto, porque una clase corta pesa mucho menos que un style en línea repetido en cada token. El intercambio es claro: Prism entrega marcado ligero a cambio de una hoja de estilos y algo de precisión; Shiki entrega precisión y cero CSS a cambio de un HTML más pesado.
El error más común al llegar aquí es creer que elegir Prism significa “resaltar en el cliente”. No es así. Tanto Shiki como el <Prism /> de Astro se ejecutan en el build o el servidor y entregan HTML terminado; ninguno envía por defecto un resaltador al navegador. Cambiar de Shiki a Prism mueve la aguja del peso del HTML y de la precisión, pero no la del momento en que se resalta. El eje servidor/cliente es otro, y es el que de verdad decide el rendimiento.
Shiki o Prism: el intercambio dentro del servidor
Dentro de la rama “resaltar en el servidor”, la elección entre Shiki y Prism se resuelve mirando qué te duele más. Si el peso del HTML domina tu presupuesto —páginas con muchísimo código, donde los estilos en línea de Shiki inflan el documento, y más aún con temas duales que duplican las variables—, Prism con su marcado por clases puede salir a cuenta. Si en cambio te importa la fidelidad exacta con tu editor, la cobertura de lenguajes difíciles y no querer mantener una hoja de estilos de tema, Shiki es la elección natural, y por eso es el valor por defecto.
Un par de ejemplos aterriza la decisión. Un sitio de documentación técnica, cuya página media es medio texto y medio código, se beneficia de la precisión de Shiki: el lector espera que el color coincida con su editor, y la ausencia de una hoja de tema es una pieza menos que mantener. Un blog con algún bloque de código ocasional, en cambio, apenas notará la diferencia de precisión y sí agradecerá un HTML más liviano si el rendimiento está muy ajustado. La elección no es ideológica: depende de cuánto código lleva tu página y de cuánto pesa cada byte en tu contexto.
Shiki: preciso, sin CSS
Gramaticas TextMate, color en linea, cero hoja de estilos. HTML mas pesado a cambio.
Prism: ligero, con CSS
Tokens por clase, HTML compacto, pero exige incluir una hoja de estilos de tema.
Ambos en el servidor
Los dos se resuelven en build o render y entregan HTML sin JavaScript de cliente.
Elige por lo que duele
Peso del HTML frente a precision y cero CSS: ese es el intercambio real entre ambos.
Conviene no exagerar el coste de Shiki. Su tokenización con Oniguruma en WebAssembly no es gratis y, en sitios con muchos bloques, se nota en el tiempo de build; pero es un coste que pagas una vez, en tu máquina, y que Astro amortigua con caché. El HTML más pesado importa solo cuando el código domina la página; para la mayoría de los sitios, la precisión y la ausencia de CSS de tema pesan más que unos kilobytes de estilos en línea.
Hay además una consideración de arranque. El motor WebAssembly de Shiki y la carga de sus gramáticas tienen un coste inicial que se amortiza a lo largo de la compilación: en proyectos pequeños es imperceptible, en enormes se nota al principio del build. Prism, más liviano, arranca antes. Pero de nuevo ese gasto es de tu máquina y de una sola vez por build, no del visitante, así que rara vez debe inclinar la decisión frente a la precisión o el peso del HTML, que sí se cobran en cada visita. La regla se mantiene: optimiza lo que paga el visitante, no lo que paga tu servidor de integración continua.
¿Cliente o servidor? Cuándo cada uno
Aquí está la pregunta que de verdad decide el rendimiento, y es independiente de si usas Shiki o Prism. Resaltar en el servidor —lo que hemos hecho durante todo el nivel— tiene sentido siempre que el código se conozca antes de la petición: contenido estático, ejemplos de documentación, fragmentos escritos a mano. El cálculo corre una vez, el resultado se reutiliza en cada visita, y el navegador no gasta nada.
Resaltar en el cliente solo tiene sentido cuando el código no se conoce hasta que el navegador está vivo: un editor de código en directo donde el usuario teclea, un fragmento generado por el propio usuario y mostrado al instante, contenido traído por una API y renderizado dentro de una isla interactiva, o funciones que exigen ejecución en el navegador. En esos casos no hay build ni servidor que pueda adelantarse, porque la entrada aún no existía; entonces, y solo entonces, envías un resaltador al cliente y pagas su coste.
Cuando ese caso llega, lo que envías importa tanto como la decisión de enviarlo. Un resaltador de cliente carga su motor y, con él, las gramáticas de los lenguajes que vayas a resaltar y el tema elegido. Shiki ofrece para esto una construcción de grano fino, donde importas solo el núcleo y las gramáticas concretas que uses en lugar del paquete entero, precisamente para que el coste inevitable sea el mínimo posible. La disciplina, en la rama del cliente, es esa: si no puedes evitar enviar un resaltador, envía el más pequeño que resuelva tu caso, y cárgalo de forma diferida para que no bloquee la primera pintura de la página.
En el modelo de Astro, ese resaltado de cliente vive dentro de una isla: un componente de framework marcado con una directiva client: que lo hidrata en el navegador. El resaltador se carga ahí, junto a la lógica del editor, y solo ahí; el resto de la página sigue siendo HTML estático sin JavaScript.
---
import EditorEnVivo from '../components/EditorEnVivo.jsx';
---
<EditorEnVivo client:load />
Así, incluso cuando el resaltado baja al cliente, su coste queda acotado a la porción que de verdad lo necesita, en lugar de contaminar todo el documento. Un editor incrustado como CodeMirror o Monaco trae su propio resaltado pensado para texto que cambia con cada pulsación; y si quieres la fidelidad de Shiki en vivo, su construcción de grano fino permite montar el resaltador en esa misma isla. El patrón no cambia: el resaltado se vuelve parte de la lógica interactiva de una isla concreta, con su coste asumido a conciencia porque no existe alternativa.
flowchart TD
Q{el codigo se conoce antes de la peticion} -->|si estatico| SRV[resalta en servidor una vez]
Q -->|si por peticion| REQ[resalta en el render bajo demanda]
Q -->|no runtime dinamico| CLI[resalta en el cliente con JS]
SRV --> HTML[HTML estatico cero JS]
REQ --> HTML
CLI --> COST[descarga motor y gramaticas y corre por visita]
style SRV fill:#a6e3a1,color:#11111b
style HTML fill:#a6e3a1,color:#11111b
style COST fill:#f38ba8,color:#11111bRendimiento: qué paga quién
El modelo de coste lo aclara todo. Resaltar en el servidor concentra el gasto en un único pago —CPU del build o del render— y entrega HTML que sirve a infinitos visitantes sin recalcular; el navegador solo pinta. Resaltar en el cliente reparte el gasto entre todos los visitantes: cada dispositivo descarga el motor y sus gramáticas, y ejecuta el resaltado en cada visita, con su coste en ancho de banda, en tiempo hasta ser interactivo y hasta en batería. La conclusión práctica se ordena sola: por defecto, resalta en el servidor; recurre al cliente únicamente cuando el código sea genuinamente dinámico en tiempo de ejecución, y aun entonces, minimiza lo que envías.
Merece la pena poner cifras al intercambio. El HTML de Shiki es más pesado que el de Prism —estilos en línea por token frente a clases cortas—, pero ambos se miden en kilobytes de texto que se comprimen bien y se sirven una vez. Un resaltador de cliente, en cambio, se mide en decenas o cientos de kilobytes de JavaScript que hay que descargar, analizar y ejecutar en cada dispositivo. La asimetría es de órdenes de magnitud, y siempre a favor del servidor: incluso el HTML más inflado de Shiki resulta más barato para el visitante que el resaltador de cliente más ajustado. Esa es, en el fondo, la razón por la que Astro elige resaltar en el servidor por defecto y por la que tú deberías desviarte de esa opción solo con una razón de peso.
Ante un bloque de código, hazte tres preguntas en orden. Primera: ¿se conoce su contenido antes de la petición? Si sí, resáltalo en el servidor y no le des más vueltas. Segunda: ¿domina el código el peso de la página y necesitas cada byte? Entonces, dentro del servidor, sopesa Prism frente a Shiki. Tercera: ¿el contenido lo produce el usuario en tiempo real? Solo entonces baja al cliente, con el resaltador más pequeño que puedas. Las preguntas van de la más decisiva a la más secundaria, y casi siempre la primera ya zanja el asunto.
El resaltado de código, tan modesto, resulta ser un caso de estudio perfecto de la pregunta que gobierna toda la arquitectura de rendimiento: dado un cálculo, ¿en qué etapa del ciclo de vida —build, servidor en la petición, o cliente— debe correr? Y la respuesta no es cuestión de gusto ni de librería; la dictan dos variables medibles. La primera es cuándo se conocen sus entradas: un cálculo no puede correr antes de que exista aquello de lo que depende. La segunda es cuántas veces se reutiliza su salida: cuanto más se reaproveche un resultado, más caro es recalcularlo y más barato precalcularlo. Cruza ambas y el lugar correcto aparece casi solo. El código de un bloque estático tiene entradas conocidas en el build y una salida reutilizada por cada visitante: pertenece al build, sin discusión. El código que depende de la petición —una variante por usuario, un dato de sesión— no se conoce hasta que llega la petición, pero sí antes que el navegador: pertenece al render del servidor. Y el código que el usuario teclea ahora, en un editor en vivo, solo tiene una entrada que únicamente el cliente posee, y por tanto no hay otra opción que calcularlo en el cliente: ahí el JavaScript es inevitable, y el oficio ya no está en evitarlo sino en enviar lo mínimo. Fíjate en que la elección entre Shiki y Prism es un intercambio pequeño que vive entero dentro de una sola de esas etapas, la del servidor: precisión y cero CSS frente a HTML ligero y una hoja compartida. Es una decisión real, pero de segundo orden. La decisión de primer orden, la que de verdad mueve las métricas, es la de la etapa. El ingeniero maduro no pregunta “¿cuál es el mejor resaltador?”; pregunta “¿dónde pertenece este cálculo, y qué es lo más barato y correcto que puedo enviar desde ahí?”. Y esa pregunta no es sobre resaltado: es la forma de Astro entera, una máquina para empujar cada cómputo tan pronto como sus entradas lo permitan y entregar a la etapa siguiente solo lo estrictamente necesario. El mismo bloque de código puede ser un artefacto de build de coste nulo o una carga de JavaScript en cada visita, y cuál de los dos acaba siendo no lo decide la librería que elijas, sino dónde decides que viva el cálculo. Aprender a hacer bien esa elección —para el resaltado y para todo— es, al final, de lo que trataba todo el nivel.
- Cambia una página a Prism con
syntaxHighlight: 'prism', incluye una hoja de estilos de tema, y compara el peso del HTML con el que producía Shiki. - Renderiza un fragmento con
<Prism />y confirma en la red del navegador que sigue siendo resaltado de servidor, sin JavaScript de cliente. - Describe un caso que exija de verdad resaltado en el cliente —un editor en vivo, por ejemplo— y enumera qué tendrías que enviar al navegador.
- Para tres bloques distintos —uno estático, uno dependiente de la petición y uno tecleado por el usuario— argumenta en qué etapa debe correr cada resaltado según cuándo se conocen sus entradas.