Jair Rivera · Blog

Proyecto Web — PHP + Vite + Tailwind + CI/CD

Esta documentación describe un proyecto web construido con PHP clásico (archivos .php planos), usando Vite como bundler para JavaScript y CSS, Tailwind CSS 3 como framework de estilos y un pipeline CI/CD en GitHub Actions que compila el front-end y despliega automáticamente los archivos a un hosting compartido vía FTP.

El objetivo es tener una landing / sitio informativo con PHP, pero con tooling moderno (Vite + Tailwind) y un flujo de despliegue profesional: cada vez que se hace git push main, el sitio se reconstruye y se actualiza en el servidor sin subir archivos manualmente.

Ver sitio (demo genérico)

Objetivos del sistema

Los objetivos principales del proyecto son:

  • Seguir usando PHP simple (sin frameworks grandes) para las vistas y la lógica básica, pero con un front-end moderno.
  • Integrar Tailwind CSS 3 en modo compilado, con purga automática de clases leyendo directamente el HTML y las vistas PHP.
  • Aprovechar Vite para:
    • Compilar y minificar CSS y JS.
    • Generar assets con hash para cache busting (por ejemplo main-XXXXXX.js).
    • Usar npm run dev con recarga rápida en local.
  • Conectar Vite con PHP mediante un loader que lea el manifest.json y genere correctamente las etiquetas <link> y <script> en producción.
  • Configurar un pipeline CI/CD que:
    • Se ejecute automáticamente al hacer push a la rama main.
    • Efectúe la instalación de dependencias y el build de Vite.
    • Suba solo los archivos compilados (dist) y PHP necesarios vía FTP a un hosting compartido (por ejemplo Hostinger).
  • Mejorar el rendimiento y PageSpeed, evitando CSS y fuentes bloqueantes de terceros (como Google Fonts) y sirviéndolo todo desde el propio servidor.

Tecnologías y herramientas utilizadas

  • PHP 8+ — motor principal del sitio y lógica de las vistas.
  • Vite — bundler para JS/CSS, encargado de generar la carpeta dist/ con assets minificados y el manifest.json.
  • Tailwind CSS 3 — framework CSS utility-first, configurado con purga de clases en función de archivos .php y src/.
  • Node.js 20.19.x — versión utilizada en el runner de GitHub Actions para ejecutar Vite y Tailwind.
  • GitHub + GitHub Actions — repositorio remoto y orquestador del pipeline CI/CD.
  • SamKirkland/FTP-Deploy-Action — acción de GitHub para el deploy por FTP/FTPS.
  • Hosting compartido (ej. Hostinger) — servidor donde se suben los archivos finales a /public_html.
  • Fuentes locales (Figtree) — tipografía servida desde el propio servidor para evitar bloqueo de render por Google Fonts.

Arquitectura de carpetas y estructura general

A nivel de desarrollo local (por ejemplo dentro de htdocs), la estructura del proyecto se organiza aproximadamente así:

proyecto-web-php-vite-tailwind/
├── index.php
├── contacto.php
├── nosotros.php
├── vite.loader.php
├── src/
│   ├── main.js
│   ├── input.css       // Tailwind + estilos base
│   └── fonts.css       // Definición de fuente local (Figtree)
├── public/
│   └── fonts/
│       └── Figtree-VariableFont_wght.ttf
├── package.json
├── package-lock.json
├── vite.config.js
├── tailwind.config.js
├── postcss.config.js
└── .github/
    └── workflows/
        └── deploy.yml  // Pipeline CI/CD de GitHub Actions

Tras ejecutar npm run build en local o en el servidor de GitHub Actions, Vite genera la carpeta dist/, y en el hosting la estructura final queda aproximadamente así (el contenido de dist/ se sube al /public_html):

public_html/
├── index.php
├── contacto.php
├── nosotros.php
├── vite.loader.php
├── .vite/
│   └── manifest.json   // Mapa de assets generado por Vite
└── assets/
    ├── main-XXXXXX.css
    ├── main-XXXXXX.js
    └── ...otros assets (imágenes, fuentes procesadas, etc.)

Integración de Vite + Tailwind en un proyecto PHP

Aunque Vite suele usarse con React, Vue o similares, aquí se utiliza como herramienta de build para una web en PHP “clásico”: se encarga de compilar el CSS de Tailwind, empaquetar el JS y generar el manifest de assets.

Punto de entrada: src/main.js

// src/main.js
import './input.css';
import './fonts.css';

console.log('Vite + Tailwind + PHP funcionando');

Hoja principal de estilos: src/input.css

@tailwind base;
@tailwind components;
@tailwind utilities;

body {
  @apply font-sans antialiased;
}

Fuente local: src/fonts.css

En lugar de depender de Google Fonts, se usa una fuente local (por ejemplo Figtree) para evitar peticiones bloqueantes externas:

/* src/fonts.css */
@font-face {
  font-family: "Figtree";
  src: url("/fonts/Figtree-VariableFont_wght.ttf") format("truetype");
  font-weight: 100 900;
  font-display: swap;
}

Configuración de Vite para build y manifest

Vite se configura para generar una carpeta dist/ con un manifest.json y rutas relativas para los assets:

// vite.config.js
import { defineConfig } from 'vite';

export default defineConfig({
  build: {
    outDir: 'dist',
    manifest: true,           // Genera dist/.vite/manifest.json
    rollupOptions: {
      input: 'src/main.js',   // Punto de entrada principal
    },
    emptyOutDir: true,
  },
});

El manifest resultante suele tener esta forma:

{
  "src/main.js": {
    "file": "assets/main-DBIKJFer.js",
    "src": "src/main.js",
    "isEntry": true,
    "css": [
      "assets/main-BUXrEYNY.css"
    ]
  }
}

Nótese que Vite guarda rutas relativas (por ejemplo "assets/main-..."), así que el código PHP debe respetarlas tal cual y no concatenar /assets/ dos veces.

Tailwind CSS 3 y purga de estilos en un sitio PHP

Tailwind 3 se configura para analizar tanto los archivos de src/ como las vistas .php, de forma que solo genere las utilidades realmente usadas.

// tailwind.config.js
export default {
  content: [
    "./*.php",
    "./**/*.php",
    "./src/**/*.{js,ts}"
  ],
  theme: {
    extend: {
      fontFamily: {
        sans: ['Figtree', 'sans-serif'],
      },
    },
  },
  plugins: [],
};

Esto garantiza que el CSS final sea muy compacto y solo contenga las utilidades necesarias para las clases Tailwind que aparecen en los archivos PHP y JS. Esta purga automática es una de las razones por las que tiene sentido usar CI/CD: cada vez que cambian las vistas, el CSS debe reconstruirse correctamente en producción.

Loader PHP ↔ Vite: uso de manifest.json

Para conectar el build de Vite con las vistas PHP, se crea un archivo vite.loader.php que:

  • En producción, lee .vite/manifest.json y construye las rutas a CSS/JS.
  • En desarrollo, utiliza directamente el Vite Dev Server (http://localhost:5173).
<?php
function loadViteAssets(string $entry = "src/main.js") {

    // Manifest generado por Vite en producción
    $manifestPath = __DIR__ . "/.vite/manifest.json";

    // 🚀 PRODUCCIÓN
    if (file_exists($manifestPath)) {

        $manifest = json_decode(file_get_contents($manifestPath), true);

        if (!isset($manifest[$entry])) {
            return "<!-- Entry $entry no encontrado en manifest -->";
        }

        // El manifest ya trae rutas tipo "assets/main-XXXX.css"
        $css = $manifest[$entry]["css"][0] ?? null;
        $js  = $manifest[$entry]["file"];

        $html = "";

        // CSS
        if ($css) {
            $html .= '<link rel="stylesheet" href="/' . $css . '">' . PHP_EOL;
        }

        // JS
        $html .= '<script type="module" src="/' . $js . '"></script>' . PHP_EOL;

        return $html;
    }

    // 🧑‍💻 DESARROLLO: Vite Dev Server
    return <<<HTML
<!-- VITE DEV -->
<script type="module" src="http://localhost:5173/src/main.js"></script>
HTML;
}

En cada archivo PHP (por ejemplo index.php) el loader se utiliza así:

<?php include __DIR__ . "/vite.loader.php"; ?>
<!DOCTYPE html>
<html lang="es">
<head>
  <meta charset="UTF-8">
  <title>Inicio</title>

  <?= loadViteAssets("src/main.js"); ?>
</head>

Pipeline CI/CD con GitHub Actions y FTP

El pipeline CI/CD se define en .github/workflows/deploy.yml y se ejecuta cada vez que se hace push a la rama main. El flujo básico es:

  1. Descargar el repositorio.
  2. Instalar Node.js (versión compatible con Vite).
  3. Instalar dependencias con npm ci.
  4. Ejecutar npm run build para generar dist/.
  5. Subir el contenido de dist/ vía FTP al /public_html del hosting.
# .github/workflows/deploy.yml
name: Deploy to Shared Hosting via FTP

on:
  push:
    branches:
      - main

jobs:
  ftp-deploy:
    name: Build & Upload Site
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20.19.0

      - name: Install dependencies
        run: npm ci

      - name: Build project
        run: npm run build

      - name: FTP Deploy (only dist/)
        uses: SamKirkland/FTP-Deploy-Action@v3.1.1
        with:
          ftp-server: ${{ secrets.FTP_HOST }}
          ftp-username: ${{ secrets.FTP_USER }}
          ftp-password: ${{ secrets.FTP_PASSWORD }}
          local-dir: dist/
          server-dir: /public_html/

Los valores de FTP_HOST, FTP_USER y FTP_PASSWORD se almacenan como secrets en el repositorio (Settings → Actions → Secrets).

Problemas encontrados y soluciones aplicadas

Durante la configuración y puesta en marcha del pipeline, se presentaron varios problemas que se fueron resolviendo:

  • Action de FTP con versión inexistente:
    Se intentó usar SamKirkland/FTP-Deploy-Action@v4, que no estaba disponible, causando errores del tipo “unable to find version”.
    Solución: usar la versión estable SamKirkland/FTP-Deploy-Action@v3.1.1.
  • Node.js incompatible con Vite:
    Algunas versiones de Node en el runner no eran compatibles con Vite, que requiere Node 18+ o 20+.
    Solución: fijar explícitamente node-version: 20.19.0 en actions/setup-node.
  • Inputs incorrectos para la acción FTP:
    En la versión 3 de la acción, los parámetros correctos son ftp-server, ftp-username y ftp-password, no server o username.
    Solución: ajustar los nombres de los campos en el deploy.yml.
  • Desalineación entre dist/ y public_html:
    El contenido de dist/ se sube directamente a /public_html, por lo que en el servidor ya no existe la carpeta dist. Inicialmente, el loader buscaba /dist/.vite/manifest.json.
    Solución: cambiar la ruta del manifest en vite.loader.php a: __DIR__ . "/.vite/manifest.json".
  • Rutas duplicadas /assets/assets/...:
    El manifest ya incluía "assets/main-...", pero el loader concatenaba /assets/ al inicio, generando rutas como /assets/assets/main-XXXX.css.
    Solución: usar directamente el campo del manifest y solo anteponer una barra inicial: "/" . $css, "/" . $js.
  • Penalización por Google Fonts en PageSpeed:
    El uso de @import de Google Fonts generaba una petición extra que bloqueaba el renderizado inicial.
    Solución: descargar la fuente (por ejemplo Figtree), servirla localmente y definirla con @font-face en fonts.css, eliminando por completo la dependencia de Google Fonts en el sitio productivo.

Rendimiento y mejoras de PageSpeed

Tras aplicar la integración de Vite + Tailwind con purga, hosting de fuente local y el pipeline CI/CD, los resultados típicos en móviles son:

  • Rendimiento móvil cercano a 100/100 en Lighthouse.
  • FCP (First Contentful Paint) alrededor de ~1s.
  • LCP (Largest Contentful Paint) estable en ~1s.
  • TBT (Total Blocking Time) prácticamente 0 ms.
  • CLS (Cumulative Layout Shift) ≈ 0, gracias a definir tamaños de imagen y layout estable.

Aunque Lighthouse puede seguir mostrando alguna advertencia sobre CSS que “bloquea render”, esto es normal: cualquier hoja de estilos en el <head> genera cierto bloqueo mínimo. Lo importante es que el impacto sea muy bajo (pocos milisegundos) y que los recursos se sirvan de forma eficiente desde el propio servidor.

Ventajas del enfoque PHP + Vite + Tailwind + CI/CD

  • Mantener PHP simple: se conserva un stack muy fácil de desplegar en cualquier hosting compartido, sin necesidad de Node en el servidor.
  • Front-end moderno: se aprovechan Vite y Tailwind para tener CSS/JS optimizados, recarga rápida en desarrollo y assets agrupados por hash para cache busting.
  • Purga automática de CSS: Tailwind analiza los archivos .php y src/, generando solo las clases necesarias, lo que reduce el tamaño del CSS final.
  • Deploy profesional: con el pipeline CI/CD, cada push a main desencadena: build → deploy → sitio actualizado, sin FTP manual.
  • Menos errores humanos: se evita olvidar subir el manifest.json, el CSS actualizado, o mezclar builds viejos con código nuevo.
  • Buen rendimiento en mobile: fuentes locales, assets minificados y HTML/JS livianos se traducen en buenas métricas de Lighthouse y mejor UX.
  • Base escalable: es fácil añadir nuevas páginas PHP, más JS o más estilos, manteniendo la misma arquitectura y el mismo pipeline de despliegue.

Resultados y aprendizajes

Este proyecto demuestra que es totalmente viable:

  • Combinar PHP clásico con un stack moderno de front-end (Vite + Tailwind) sin migrar a frameworks complejos.
  • Usar CI/CD con GitHub Actions incluso en hostings compartidos basados en FTP.
  • Conseguir métricas de rendimiento altas (especialmente en mobile) mediante:
    • Purga de CSS de Tailwind.
    • Fuentes locales con font-display: swap.
    • Imágenes optimizadas y tamaños definidos.
  • Organizar un flujo de trabajo cómodo: desarrollo en local con npm run dev + confirmación visual + git push main → deploy automático.
  • Aprender a interpretar y corregir problemas típicos de pipelines: versiones de Node, versiones de actions, rutas de assets, manifest, etc.