app.config.ts: configurar el proyecto y su despliegue
El único fichero de configuración de SolidStart. defineConfig de @solidjs/start/config envuelve a Vinxi y expone una superficie declarativa sobre las tres capas: la opción ssr elige entre renderizar en el servidor o no, el bloque server pasa a Nitro el preset de despliegue y reglas como prerender, y el bloque vite afina el bundler y sus plugins, con forma de función para configurar por router. El gran superpoder es el preset de Nitro: el mismo proyecto compila para Node, Vercel, Netlify, Cloudflare, Deno o una salida estática cambiando una sola línea.
Toda la configuración de un proyecto SolidStart cabe en un fichero: app.config.ts. Esa concentración no es pobreza sino diseño —recuerda que debajo hay tres herramientas, y este archivo es la superficie declarativa única desde la que se afinan las tres—. Aprender a leerlo es aprender a hablar con cada capa por su bloque: ssr decide si renderizas en el servidor, server es la puerta a Nitro y a su superpoder de los presets de despliegue, y vite es la puerta al bundler. El pago de haberse construido sobre Nitro se cobra aquí, en una línea que convierte el mismo código en un artefacto para Node, para un edge worker o para un bucket estático, sin que toques tu aplicación.
- Entender que
defineConfigde@solidjs/start/configenvuelve a Vinxi y unifica las tres capas. - Elegir el destino de despliegue con el preset de Nitro y saber que es intercambiable.
- Afinar Vite y sus plugins, incluida la forma de función que configura por router.
- Situar opciones clave:
ssr,prerender,middlewarey las reglas de servidor.
defineConfig: la superficie declarativa única
El fichero exporta el resultado de defineConfig, una función que envuelve a la de Vinxi y le añade los valores por defecto de SolidStart. Un proyecto recién creado no necesita nada dentro:
// app.config.ts
import { defineConfig } from "@solidjs/start/config";
// vacio ya es un proyecto valido: SSR en streaming, preset por defecto
export default defineConfig({});
A partir de ese objeto vacío, cada clave que añades es una instrucción para una de las capas. Las tres que importan de entrada son ssr para el modo de render, server para todo lo relativo a Nitro y vite para el bundler. Conviene tener siempre presente ese reparto: cuando busques dónde poner una opción, primero pregúntate a qué capa pertenece, y el bloque se elige solo.
flowchart LR CFG[app.config.ts] --> S1[ssr elige el modo de render] CFG --> S2[server habla con Nitro] CFG --> S3[vite afina el bundler] S2 --> P[preset y prerender y reglas] S3 --> V[plugins alias y build] style CFG fill:#89b4fa,color:#11111b style S2 fill:#f9e2af,color:#11111b style S3 fill:#a6e3a1,color:#11111b
El superpoder: presets de despliegue
El bloque server se entrega tal cual a Nitro, y su opción más valiosa es preset. Un preset es la receta que Nitro usa para empaquetar tu build hacia una plataforma concreta: cambia el formato del servidor, las APIs disponibles y el artefacto final. Sin preset explícito, el valor por defecto es node-server, que produce un servidor Node ejecutable en .output.
import { defineConfig } from "@solidjs/start/config";
export default defineConfig({
server: {
// el mismo codigo, otro destino: Cloudflare como modulo Worker
preset: "cloudflare-module",
},
});
La lista de presets de Nitro es larga y compartida con todo el ecosistema UnJS: node-server, vercel, netlify, cloudflare-pages, cloudflare-module, deno-deploy, bun, aws-lambda y static, entre otros. Y como el preset también se puede fijar por variable de entorno, tu pipeline de CI puede desplegar el mismo commit a destinos distintos sin tocar el repositorio:
# el mismo proyecto, otro destino, sin editar app.config.ts
SERVER_PRESET=vercel npm run build
Para un sitio sin partes dinámicas, el preset static genera una carpeta de ficheros servible desde cualquier CDN. Y aunque uses un preset de servidor, puedes pre-renderizar rutas concretas a HTML en el build con server.prerender, indicando las rutas o dejando que crawlLinks las descubra siguiendo enlaces. Es el punto medio entre SSR por petición y estático total: las páginas que no cambian se congelan en el build y las que sí dependen del servidor se rinden en vivo.
export default defineConfig({
server: {
prerender: {
routes: ["/", "/sobre", "/blog"],
crawlLinks: true,
},
},
});
Afinar Vite y sus plugins
El bloque vite se funde con la configuración del bundler, y es donde añades plugins, alias de resolución o ajustes de build. Añadir Tailwind, por ejemplo, es registrar su plugin de Vite aquí:
import { defineConfig } from "@solidjs/start/config";
import tailwindcss from "@tailwindcss/vite";
export default defineConfig({
vite: {
plugins: [tailwindcss()],
},
});
Hay un matiz que solo tiene sentido recordando la lección primera: Vinxi no compila un único bundle, sino varios routers de build —cliente, servidor, funciones de servidor—. Por eso vite acepta también la forma de función, que recibe cuál es el router en curso y te deja devolver una configuración distinta para cada uno. Es la vía para, por ejemplo, aplicar un plugin solo al cliente.
export default defineConfig({
vite({ router }) {
// router puede ser client, server o server-function
return {
resolve: { alias: { "~": "/src" } },
};
},
});
Más allá del preset, server acepta cualquier opción de Nitro, y ahí hay potencia de sobra para producción: routeRules para fijar cabeceras, redirecciones o caché por patrón de ruta; storage para declarar backends de almacenamiento con clave y valor; o compatibilityDate para congelar el comportamiento del runtime. No necesitas nada de esto para empezar, pero saber que server es la ventana completa a Nitro te evita buscar en SolidStart opciones que en realidad documenta Nitro.
Dos claves más redondean el fichero. ssr conmuta el modo de render —true por defecto, false para SPA—, tema de la última lección. Y middleware apunta a un fichero que intercepta cada petición antes de las rutas, el sitio natural para autenticación o cabeceras globales.
export default defineConfig({
ssr: true,
middleware: "./src/middleware.ts",
server: { preset: "netlify" },
});
El middleware es código de servidor que corre en cada petición antes de que el enrutador elija una ruta, así que es el sitio natural para resolver la sesión, imponer redirecciones o poblar el locals de la petición que luego leerás con getRequestEvent. Y para los ajustes finos del compilador de Solid en sí —no de Vite en general— existe el bloque solid, que pasa opciones directamente a vite-plugin-solid; rara vez lo tocarás, pero saber que está evita que busques esas opciones en el sitio equivocado.
Una lectura por capas
La forma disciplinada de trabajar este fichero es no verlo como una lista plana de opciones, sino como tres conversaciones separadas que comparten un archivo. Cuando algo no encaja, la pregunta correcta nunca es qué opción de SolidStart lo resuelve, sino a qué capa pertenece el problema: si es de compilación va a vite, si es de servidor o despliegue va a server, y si es de dónde se renderiza va a ssr. Ese reflejo te ahorra buscar en la documentación equivocada y te deja componer configuraciones que combinan las tres capas sin confundir sus fronteras.
Merece la pena interiorizar también que este fichero se evalúa en tiempo de build, no en cada petición: es JavaScript que corre una vez cuando Vinxi arranca o empaqueta, de modo que puedes leer variables de entorno o calcular valores dentro de él, pero no depende del ciclo de una solicitud concreta. Esa distinción explica por qué el preset se puede fijar por variable de entorno del proceso de build y por qué la configuración es estática respecto al tráfico: cuando el servidor ya está sirviendo, app.config.ts hace mucho que terminó su trabajo.
Lo que decides por petición —autenticación, cabeceras condicionales, redirecciones según el usuario— no vive aquí sino en el middleware o en las propias rutas, que sí corren en cada solicitud. Confundir ambos planos es un error sutil pero caro: intentar ramificar la configuración según datos de una petición no funciona, porque para cuando llega la primera petición este archivo ya es historia. Reserva app.config.ts para lo que es cierto de todo el despliegue y deja lo que cambia por solicitud para las capas que viven en el tiempo de la petición.
La tentación al mirar app.config.ts es leerlo como el archivo de opciones de un framework, una bolsa de ajustes que memorizas. La lectura correcta es otra: es una superficie única sobre tres herramientas independientes, y cada bloque de nivel superior es una conversación con una de ellas. Entenderlo así reorganiza todo lo que puedes hacer con él. El bloque server no es de SolidStart, es Nitro entero asomándose por una ventana, y por eso su documentación real vive en Nitro y su catálogo de presets es el del ecosistema UnJS al completo; el día que aparezca una plataforma nueva, tu proyecto podrá desplegarse en ella sin que SolidStart publique una versión, porque quien añade el preset es Nitro. El bloque vite es el bundler asomándose por otra ventana, con todo su universo de plugins a tu disposición y con la sutileza de la forma de función, que solo cobra sentido cuando recuerdas que Vinxi orquesta varios routers de build y que a veces quieres configurar cada uno por separado. Y ssr es el interruptor que decide si tu aplicación tiene siquiera una mitad de servidor. Cuando interiorizas este reparto, dejas de buscar a ciegas dónde va una opción: te preguntas a qué capa pertenece el problema y el bloque se elige solo. Ese es también el motivo de que un solo fichero baste para gobernar algo tan complejo como un despliegue multiplataforma con SSR selectivo y un pipeline de build a medida: no es que SolidStart haya condensado un océano de configuración en un archivo, es que ha puesto tres océanos detrás de tres puertas y te ha dado la llave de cada una. Programar la configuración con soltura es, al final, saber en todo momento con cuál de las tres capas estás hablando.
- Parte de un
defineConfigvacío y añade unserver.presetdistinto denode-server; razona qué cambia en el artefacto de.output. - Fija el preset por variable de entorno con
SERVER_PRESETen el comando de build y comprueba que el código no cambia entre dos destinos. - Pre-renderiza tres rutas con
server.prerendery verifica cuáles se sirven como HTML estático y cuáles siguen pasando por el servidor. - Registra un plugin de Vite —por ejemplo Tailwind— y luego reescribe el bloque
viteen forma de función para aplicar un alias de resolución. - Clasifica cinco necesidades —añadir un plugin, cambiar de plataforma, cachear una ruta, apuntar a un middleware, desactivar SSR— en el bloque
vite,serverossrque le corresponde.