Madraset El Shamamsa, Part 2: Architecture, Docker, and Vite
Madraset El Shamamsa combines a traditional PHP application with a modern asset workflow and edge delivery. The architecture is intentionally evolutionary: Vite and containerized development improve the system around the existing content model instead of requiring a wholesale frontend rewrite.
Production request and data flow
flowchart TB
Client["Browser or crawler"] --> CF["Cloudflare proxy, TLS, and edge cache"]
CF -->|"Cache miss or dynamic route"| Origin["Apache and PHP origin"]
Origin --> Router["Page entrypoint or API endpoint"]
Router --> Composer["Library generate-* assembler"]
Composer --> Shared["Shared shell and library components"]
Composer --> MySQL[("MySQL")]
Shared --> Response["Rendered HTML response"]
Response --> CF
CF --> Client
Client -->|"Hashed CSS and JavaScript request"| AssetHost["Versioned production assets"]
AssetHost -->|"Asset response"| Client
Client -->|"Images, audio, PDFs request"| R2["Cloudflare R2 asset host"]
R2 -->|"Media response"| Client
Cloudflare is the public entry layer. Cacheable responses can be served at the edge; otherwise the request reaches Apache and PHP. A content page typically begins with a small entrypoint that includes a library-specific assembler. That assembler loads data, builds the shared page shell, and returns complete HTML. MySQL supports content and application data, while large media is decoupled from the PHP origin through R2.
Search is part of the reading experience but not the core request path shown above. Library records are exported for a dedicated search integration, allowing search results to point back to the canonical server-rendered pages.
Local development is a system, not a single process
The repository's Docker Compose file captures the services needed for representative local work:
flowchart LR
Developer["Developer browser"] -->|":8080"| Web["PHP 8.4 / Apache container"]
Developer -->|":5173 HMR"| Vite["Vite container"]
Developer -->|":8081"| PMA["phpMyAdmin"]
Web --> DB[("MySQL 8")]
PMA --> DB
Web -->|"assistant proxy calls"| Worker["Cloudflare assistant Worker :8787"]
Web -. "shared source volume" .-> Vite
The PHP site is mounted into both the web and Vite containers. During development, PHP emits Vite client and module tags when the development-server flag is enabled, so HMR can update frontend work without rebuilding the PHP application. MySQL has a health check before dependent services start; phpMyAdmin provides an optional inspection surface. The assistant Worker runs separately because its runtime and deployment lifecycle differ from the PHP origin.
Docker does not make local and production infrastructure identical. It makes the application dependencies and service relationships explicit enough to reproduce common development paths.
Vite's production responsibility
Vite does not render article pages. Its production build starts from the frontend application entry, processes JavaScript, CSS, and image imports, then writes a manifest and content-hashed files. The PHP helper reads that manifest and emits the correct production tags.
flowchart LR
Source["frontend/app.js and imported assets"] --> Build["Vite production build"]
Build --> Manifest["build/manifest.json"]
Build --> Hashed["Content-hashed JS/CSS/assets"]
PHP["PHP Vite helper"] --> Manifest
Manifest --> Tags["Production script and stylesheet tags"]
Hashed --> CDN["Static asset host / CDN"]
Tags --> Browser["Browser requests versioned files"]
CDN --> Browser
Hashed filenames make long-lived caching safe: a changed asset gets a new URL instead of relying on users to invalidate an old file. In development, the same helper selects the Vite server; in production, it selects the manifest. This small interface keeps build-tool details out of individual PHP templates.
Why this boundary is useful
The stack has three clearly different jobs:
- The PHP composition layer owns documents, metadata, and server-side data access.
- Vite owns the frontend module graph and versioned production assets.
- Cloudflare and R2 own edge delivery for appropriate public responses and media.
Keeping those concerns separate allows incremental changes. Frontend modules can move into Vite without converting every page to a SPA, and shared PHP components can evolve without coupling page composition to the asset bundler.