El tema que trataremos en este post es la implementación de una arquitectura de temas dinámicos (theming) dentro de un proyecto monolítico de Laravel 12+, permitiendo que una única base de código y base de datos sirvan a múltiples interfaces de usuario bajo dominios diferentes.
La clave de esta implementación reside en el uso estratégico de un Middleware personalizado. Este componente interceptara cada petición HTTP y mediante la lectura del host o subdominio solicitado (azul.misitio.com o rojo.misitio.com), determina el tema activo para esa sesión. Una vez identificado el tema, el middleware no solo establece una variable global para el tema, sino que también manipula la configuración interna del Buscador de Vistas de Laravel. Esta manipulación es crítica, ya que le indica a Laravel que priorice las rutas de las vistas específicas del tema activo antes de buscar las vistas estándar.
La eficiencia y flexibilidad del sistema se basan en el principio de «fallback» o mecanismo de reserva. Se establecera una carpeta de vistas default/ que contiene la versión base y completa de todas las plantillas de la aplicación. Ademas se crearan directorios individuales para cada Theme que solo necesitan contener las vistas que se quiera personalizar en su diseño a las del resto. Si un tema específico carece de una plantilla, el sistema de búsqueda de vistas automáticamente recurre a la versión almacenada en la carpeta default/, garantizando que la aplicación nunca falle en renderizar contenido, mientras mantiene el código modular y reduce la duplicación innecesaria.
Manos a la obra.
Se necesita:
Laragon está diseñado para que cada proyecto en la carpeta www tenga un solo dominio virtual automático (en nuestro caso es, example-theme.test). Para que un solo proyecto, como «example-theme», responda a múltiples dominios virtuales (azul.test y rojo.test), necesitamos modificar manualmente el archivo de configuración del Virtual Host.
Paso 1. Deshabilitar la Creación Automática de Laragon
Abre la dirección; «C:\laragon\etc\apache2\sites-enabled\» y busca el archivo «auto.example-theme.test.conf» , a continuación renombra este archivo a «example-theme.test.conf», al quitar el prefijo «auto.», le indicas a Laragon que mantenga intacto el contenido de ese archivo y no lo regenere automáticamente.
2. Modificar el Archivo de Configuración del Virtual Host
Abre el archivo «example-theme.test.conf», busca la línea que contiene ServerName y la línea ServerAlias.
Añade los nuevos dominios virtuales que necesitamos (azul.test y rojo.test) a la línea ServerAlias, el archivo deve quedar así.
<VirtualHost *:80>
DocumentRoot "C:/laragon/www/example-theme/public"
# Este es el dominio principal generado por Laragon
ServerName example-theme.test
# Nuevos dominios separados por un espacio
ServerAlias azul.test rojo.test
<Directory "C:/laragon/www/example-theme/public">
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
3. Modificar el Archivo de Hosts de Windows
Navega a la siguiente ruta «C:\Windows\System32\drivers\etc» y abre el archivo hosts, necesitaras permisos de administrador.
Ve hasta el final del archivo y agrega:
127.0.0.1 azul.test 127.0.0.1 rojo.test
Guarda los cambios y cierra.
4. Recargar y Prueba
Guarda los cambios en tu archivo «example-theme.test.conf» y recarga Laragon. Prueba los tres dominios en el navegador, por el momento los tres dominios te mostraran la vista por defecto de Bienvenida de Laravel.
Si todo salio bien hasta aquí, continuemos con el proyecto en Laravel.
Paso 1. Configuración de Temas
Crea un nuevo archivo en config/ el cual se llamara themes.php, este archivo sera el encargado de almacenar los diferentes themes utilizados en proyecto asi como los dominios correspondientes a cada plantilla.
<?php
return [
// El tema por defecto si no se detecta ningún dominio
'default' => 'default',
// Mapeo de dominios a nombres de temas
'domains' => [
'azul.test' => 'azul',
'rojo.test' => 'rojo',
'example-theme.test' => 'default', // Dominio principal
],
// Ruta base donde se encuentran las vistas de los temas
'base_path' => resource_path('views/themes'),
];
2. Organización de Vistas y Assets
Crea ls siguiente estructura de archivos:
resources/
└── views/
└── themes/
├── azul/
│ ├── layouts/
│ │ └── app.blade.php // Layout para el tema Azul
│ └── show.blade.php
└── default/
├── layouts/
│ └── app.blade.php // Layout base con Assets genéricos
├── index.blade.php
└── show.blade.php
No estamos creando un directorio para el «theme rojo», no es un error, explicaremos el porque mas adelante.
show.blade.php para azul:
@extends('layouts.app')
@section('content')
<h1>Show, Tema {{ app('current.theme') }}</h1>
Lorem ipsum, dolor sit amet consectetur adipisicing elit. Quos repellat, asperiores necessitatibus ab cumque odio officiis optio aspernatur mollitia! Debitis tempore temporibus doloribus ab velit tenetur quia deleniti hic fuga.
@endsection
show.blade.php para default:
@extends('layouts.app')
@section('content')
<h1>Show default, Tema {{ app('current.theme') }}</h1>
@endsection
index.blade.php para default
@extends('layouts.app')
@section('content')
<h1>Index, Tema {{ app('current.theme') }}</h1>
plantilla común para todos los themes
@endsection
3. Organización de Assets
Crea los siguientes archivos:
public/
└── assets/
├── azul/
│ ├── css/
│ │ └── style.css // Estilos personalizados de Azul
│ └── js/
│ └── custom.js
├── rojo/
│ ├── css/
│ │ └── style.css // Estilos personalizados de Rojo
│ └── js/
│ └── custom.js
└── default/
├── css/
│ ├── bootstrap.min.css // Bootstrap de base
│ └── style.css
└── js/
├── custom.js
├── jquery.min.js //librerias comunes
└── bootstrap.bundle.min.js //librerias comunes
Aqui si creamos un CSS para el theme rojo.
style.css para azul:
body{
background-color:#001cc3;
color: #ffffff;
}
style.css para rojo:
body{
background-color:#c30000;
color: #ffffff;
}
custom.js para azul:
window.alert("¡Theme azul se ha cargado!");
4. Creación del Middleware de Detección de Temas (ThemeMiddleware)
Este middleware leerá el host, determinará el tema y ajustará la ruta de las vistas y compartirá el nombre del tema.
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\View;
use Illuminate\Support\Facades\Config;
class ThemeMiddleware
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next)
{
$host = $request->getHost();
$themesConfig = Config::get('themes');
//Determinar el nombre del tema
$themeName = $themesConfig['domains'][$host] ?? $themesConfig['default'];
//dd($host, $themeName); //depuración
//Establecer el nombre del tema en el contenedor de servicios
app()->instance('current.theme', $themeName);
//Configurar la ruta de las vistas
$themePath = "themes/{$themeName}";
//Priorizar la carpeta del tema activo
View::addLocation(resource_path("views/{$themePath}"));
//Agregar la carpeta por defecto como fallback (Respaldo)
if ($themeName !== $themesConfig['default']) {
View::addLocation(resource_path("views/themes/{$themesConfig['default']}"));
}
return $next($request);
}
}
5. Registro del Middleware
Los middleware en Laravel 12 se registran en bootstrap/app.php, abre el archivo y edita:
<?php
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware): void {
$middleware->append(\App\Http\Middleware\ThemeMiddleware::class);
})
->withExceptions(function (Exceptions $exceptions): void {
//
})->create();
6. Layout base
El layout base (layouts/app.blade.php) usará el nombre del tema compartido para cargar los assets correctos.
Incluye este código en todos los layouts app.blade.php:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ config('app.name') }} | {{ app('current.theme') }}</title>
@php
$theme = app('current.theme'); // Obtiene theme 'azul', 'rojo' o 'default'
$basePath = 'assets/default';
$themePath = "assets/{$theme}";
@endphp
<link rel="stylesheet" href="{{ asset("{$basePath}/css/bootstrap.min.css") }}">
<link rel="stylesheet" href="{{ asset("{$themePath}/css/style.css") }}">
</head>
<body>
@yield('content')
<script src="{{ asset("{$basePath}/js/jquery.min.js") }}"></script>
<script src="{{ asset("{$basePath}/js/bootstrap.bundle.min.js") }}"></script>
<script src="{{ asset("{$themePath}/js/custom.js") }}"></script>
</body>
</html>
7. Controlador y Rutas
Creamos un controlador llamado DummyController.php con el siguiente código:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class DummyController extends Controller
{
public function index()
{
return view('index');
}
public function show()
{
return view('show');
}
}
Y en el archivo routes/web.php modificamos de la siguiente manera:
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\DummyController;
Route::get('/', function () {
return view('welcome');
});
Route::get('/index', [DummyController::class, 'index'])->name('dummys.index');
Route::get('/show', [DummyController::class, 'show'])->name('dummys.show');
Si visitamos azul.test/show , ThemeMiddleware detecta el host y establece el tema activo como azul, ademas como el archivo show.blade.php existe en el directorio azul/, mostrara su contenido y no el de el directorio default/. Incluso mostrará un dialog que se encuentra declarado en el archivo assets/azul/js/custom.js
No asi si visitamos example-theme.test/show, solo mostrara los archivos correspondientes a ese theme.
Ahora bien, recuerdas que no creamos un directorio para el theme «rojo», aunque si declaramos archivo de estilos css para assets/rojo/css/style.css. Pues bien, lo que hace ThemeMiddleware es buscar lso archivos para el theme rojo, pero al no existir, carga el theme por defecto default, no pasa lo mismo con los estilos que si los tenemos declarados.
Similar caso ocurre cuando visitamos azul.test/index o rojo.test/index o example-theme.test/index, el archivo index.blade.php solo esta declarado en el directorio default/, por lo que es el archivo comun para todos los demas themes. Esa es la estrategia de «fallback» (reserva) y es la forma más eficiente de gestionar múltiples temas con una base de código común.
En resumen y para concluir, la implementación de temas dinámicos en Laravel 12+ vía middleware y fallback de vistas ofrece una solución elegante y de bajo acoplamiento para el desafío de ofrecer múltiples interfaces de marca. Este método optimiza el mantenimiento, ya que cualquier cambio en la lógica central o en las vistas base (default/) se propaga instantáneamente a todos los temas que no hayan sobrescrito esa plantilla específica.
Enjoy!
KolibriOS es un pequeño sistema operativo poderoso, rápido y libre con un núcleo monolítico anticipativo en tiempo real[...]
Tenia un problema, me pasaron unos archivos excel con unos cientos de registros (ver imagen más abajo) que exportaron de[...]
Tradukisto es una biblioteca para Java 8 creada para convertir números enteros o cantidades de dinero a sus equivalentes[...]
Este 2019 se implementara en Bolivia un nuevo Sistema de Facturación Electrónica con nuevas características y medidas de[...]
The Age of AI o «La era de la Inteligencia Artificial», es una serie de 8 documentales de Youtube Original, presentados[...]
Android hace uso de la base de datos SQLite para el manejo de registros en las aplicaciones. Según Santa Wikipedia defin[...]