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:

Tecnologías y herramientas utilizadas

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:

<?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:

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:

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

Resultados y aprendizajes

Este proyecto demuestra que es totalmente viable:

Regresar