Por qué `serve` mostraba un sitio feo mientras que `publishSite` estaba perfecto
Publié le 22 April 2026
¿Conoces ese momento en que tu sitio local parece nada, pero una vez desplegado en línea todo está perfecto? Eso es lo que me pasó con mi plugin Gradle JBake. La tarea`serve`mostraba botones cuadrados, texto oscuro sobre fondo negro, y un CSS extrañamente ausente. Sin embargo`publishSite`generaba el mismo sitio, él, impecable. El culpable no era ni el CSS, ni el navegador, ni la caché. Era una línea de código Kotlin — y un mal hábito con los argumentos de línea de comandos.
- toc
-
[]
La escena : cuatro screenshots, dos renderizados
Era después de un fallo del sistema. Había hecho cuatro capturas de pantalla para comparar el renderizado local (./gradlew serve`sobre`localhost:8820) con el renderizado en línea (`publishSite`en GitHub Pages). Dos capturas de pantalla por página: la parte superior e inferior.
Renderizado local
Principio de página— Los botones Project y Template sonpequeños, rectangulares, a los colores estándar (azul y verde). La tarjeta Start Writing tiene unfondo negro uniforme, sin marco.
pie de página— El subtítulo "Últimos artículos y recursos" escasi ilegible, demasiado oscuro sobre fondo negro. Las fechas debajo de las imágenes del blog ("17 October 2013", etc.) sonausentes.
Renderizado en línea
Encabezado de página— Los mismos botones songrandes, ovino, verde neón llamativo, texto en mayúsculas. La tarjeta tiene unmarco blanco redondeado con sombra proyectada.
Pie de página— El subtítulo esbien legible. Las fechas sonvisibles. Las tarjetas de los artículos tienenesquinas redondeadasy los títulos sonen negrita.
Primer reflejo: el tema. Uso un sistema de tema dinámico (claro, oscuro, alto contraste) basado en`localStorage`. Tal vez el local store activaba un tema high-contrast? No. Ya había escritohttps://cheroliv.com/blog/2025/0098_preloading_css_variable_post.html[un artículo completo sobre la precarga de temas], sé cómo funciona. Además, incluso en modo alto contraste, la estructura CSS (botones ovales, sombras) debía estar allí. No estaba.
Segundo reflejo: el caché del navegador. No, había hecho la prueba con perfiles limpios. El diff persistía.
Tercer reflejo:`serve`no servía los mismos archivos que`publishSite`.
El diagnóstico: serve no apuntaba al directorio correcto
Mi plugin Gradle`bakery`a deux tâches principales :
-
bake: genera el sitio estático en`build/bake/` -
serve: lanza un servidor web local -
`publishSite`empuja`build/bake/`a GitHub Pages
La divergencia solo podía provenir de`serve`. Si publishSite`publicaba el contenido correcto, es que`build/bake/`estaba bien. Si`serve`mostraba otra cosa, es que no servía`build/bake/.
Miremos el código. En`SiteManager.kt`, la tarea`serve`está definida así :
tasks.register("serve", JavaExec::class.java) { task ->
task.apply {
mainClass.set("org.jbake.launcher.Main")
classpath = jbakeRuntime
environment("GEM_PATH", jbakeRuntime.asPath)
jvmArgs(/* ... */)
args = listOf(
"-b", file(site.bake.srcPath).absolutePath,
"-s", layout.buildDirectory.get()
.asFile.resolve(site.bake.destDirPath)
.absolutePath
)
}
}
org.jbake.launcher.Main`es el punto de entrada de la CLI JBake 2.7.0. La idea : pasar-b`para el directorio de origen y`-s`para el directorio de destino, lanzar el modo servidor. Excepto que…
La causa raíz: JBake CLI no funciona así
JBake 2.7.0 está esperandoargumentos posicionalespara origen y destino, luego flags opcionales. La firma es :
jbake <source> <destination> [options]
Pero, en mi código, escribía:
args = listOf("-b", "/path/to/site", "-s", "/path/to/build/bake")
JBake lo interpretaba como :
-
-b→ parse el flag "bake" (booleano), consumido -
/path/to/site→ se convierte en argumento posicional 1(fuente) -
-s→ parsear la bandera "serve" (booleana), servidor iniciadoinmediatamente -
/path/to/build/bake→ se convierte en un argumento huérfano, ignorado o malinterpretado
Resultado : JBake estaba tomando`/path/to/site`como fuente,no tenía en cuenta el destinoproporcionada (build/bake), y utilizaba su repertorio por defecto (a menudo`./output`o una copia temporal). El archivo`css/styles.css`del build estaba entonces sobrescrito o ignorado, y era un CSS predeterminado antiguo (sin las variables personalizadas, sin los bordes redondeados, sin las sombras) que se servía.
En comparación,bake et publishSite`utilizan elplugin oficial de Gradle JBake(`jbake-gradle-plugin) quien escribe ordenadamente en`build/bake/`mediante la API de Gradle. No pasan por la CLI.
La solución: argumentos posicionales, no banderas
La corrección es trivial — una vez que se sabe. Basta con pasar la fuente y el destino como argumentos posicionales, entonces`-s`finalmente
args = listOf(
file(site.bake.srcPath).absolutePath,
layout.buildDirectory.get()
.asFile.resolve(site.bake.destDirPath)
.absolutePath,
"-s"
)
Eso es todo. Tres líneas cambiadas, bug resuelto.
Después de la publicación local del plugin (publishToMavenLocal), un `./gradlew serve`lanza JBake con la sintaxis correcta :
jbake /home/user/project/site /home/user/project/build/bake -s
Y esta vez, el servidor Jetty integrado a JBake sirve bien el contenido de`build/bake/`— idéntico a lo que se publica en línea.
Verificación rápida con curl
Para asegurarse de que el servidor sirva bien el contenido correcto, un simple`curl`confirma que`index.html`contiene`data-bs-theme="light"`y que`build/bake/css/styles.css`pesa bien 31 402 octets — idéntico al archivo generado por`bake`.
$ ./gradlew serve
# Dans un autre terminal :
$ curl -s http://localhost:8820/ | grep data-bs-theme
<html ... data-bs-theme="light" ...>
$ curl -I http://localhost:8820/css/styles.css
HTTP/1.1 200 OK
Content-Length: 31402
Content-Type: text/css
El renderizado local ahora espixel idénticoal renderizado desplegado.
¿Por qué este bug era vicioso?
¿Ves por qué era difícil de atrapar?
-
Sin error explícito: JBake no fallaba. Simplemente estaba funcionando con una carpeta incorrecta.
-
El build funcionaba:`./gradlew bake`generaba correctamente los archivos en`build/bake/`.
-
El despliegue funcionaba:`publishSite`publicaba el buen contenido en línea.
-
Sólo
serveestaba roto: la tarea de desarrollo, la que utilizamos constantemente para iterar. -
La costumbre de
-key value: como desarrollador, se condiciona por años de CLI Unix (-o output`,-i input). JBake CLI es una excepción — el origen y el destino son posicionales.
Conclusión
Si usted mantiene un plugin Gradle que envuelve JBake(o cualquier herramienta CLI),lee la documentación de los argumentosaunque los conozcas. Una hipótesis implícita (-s = "set destination"` puede costarte horas de depuración visual.
Aquí, el fix era literalmente cambiar :
args = listOf("-b", src, "-s", dest) // ❌ BUG
en :
args = listOf(src, dest, "-s") // ✅ FIX
Tres tokens desplazados, y mi sitio local vuelve a ser tan bonito como el sitio en producción.
Lección: cuando el renderizado difiere entre local y prod sin razón evidente, sospecha primero del pipeline — no el CSS, no el navegador, y mucho menos el framework. Suele ser el paso justo antes del renderizado el que engaña.