In the era of React and Next.js, I chose full server-side rendering with PHP for TopVideoHub. Here's why, and how I implemented it for optimal performance.
Why SSR with PHP (Not a JS Framework)
For a content-heavy video platform, SSR with PHP has clear advantages:
- SEO — Search engines see fully rendered HTML immediately. No hydration delay, no JS-dependent content.
- Performance — Zero JavaScript framework overhead. The page is ready on first paint.
- Hosting cost — PHP shared hosting costs $3-10/month. Node.js hosting starts at $5-20/month for comparable performance.
- Simplicity — No build step, no bundler configuration, no client/server code splitting.
Template Architecture
I use plain PHP templates with a simple layout system:
// app/View.php
class View {
private string $layout = 'main';
private array $data = [];
private array $sections = [];
public function render(string $template, array $data = []): string {
$this->data = $data;
extract($data);
ob_start();
require __DIR__ . "/../templates/{$template}.php";
$content = ob_get_clean();
$this->sections['content'] = $content;
ob_start();
require __DIR__ . "/../templates/layouts/{$this->layout}.php";
return ob_get_clean();
}
public function section(string $name): string {
return $this->sections[$name] ?? '';
}
public function setLayout(string $layout): void {
$this->layout = $layout;
}
}
<!-- templates/layouts/main.php -->
<!DOCTYPE html>
<html lang="<?= $lang ?? 'en' ?>">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= htmlspecialchars($title ?? 'TopVideoHub') ?></title>
<?= $this->section('head') ?>
<link rel="stylesheet" href="/assets/style.css">
</head>
<body>
<?php require __DIR__ . '/../partials/nav.php'; ?>
<main>
<?= $this->section('content') ?>
</main>
<?php require __DIR__ . '/../partials/footer.php'; ?>
<?= $this->section('scripts') ?>
</body>
</html>
Video Grid Component
<!-- templates/partials/video-grid.php -->
<div class="video-grid" data-region="<?= $region ?>">
<?php foreach ($videos as $video): ?>
<article class="video-card" data-video-id="<?= $video['video_id'] ?>">
<div class="video-facade">
<img src="<?= thumbnailUrl($video['video_id'], 'mq') ?>"
alt="<?= htmlspecialchars($video['title']) ?>"
width="320" height="180"
loading="<?= $loop_index < 4 ? 'eager' : 'lazy' ?>"
decoding="async">
<button class="play-btn" aria-label="Play <?= htmlspecialchars($video['title']) ?>">
<svg viewBox="0 0 68 48"><!-- play icon SVG --></svg>
</button>
<span class="video-duration">
<?= formatDuration($video['duration']) ?>
</span>
</div>
<div class="video-info">
<h3 class="video-title">
<a href="/watch/<?= $video['video_id'] ?>">
<?= htmlspecialchars($video['title']) ?>
</a>
</h3>
<p class="video-channel">
<?= htmlspecialchars($video['channel_title']) ?>
</p>
<p class="video-meta">
<span><?= formatViewCount($video['view_count']) ?> views</span>
<span><?= timeAgo($video['published_at']) ?></span>
</p>
</div>
</article>
<?php endforeach; ?>
</div>
Progressive Enhancement
The page works without JavaScript. JS enhances the experience:
// Progressive enhancement: only runs if JS is available
(function() {
// Lazy-load video embeds on click
document.querySelectorAll('.video-facade').forEach(facade => {
facade.addEventListener('click', function() {
const id = this.closest('.video-card').dataset.videoId;
const iframe = document.createElement('iframe');
iframe.src = `https://www.youtube.com/embed/${id}?autoplay=1`;
iframe.allow = 'autoplay; encrypted-media';
iframe.allowFullscreen = true;
this.innerHTML = '';
this.appendChild(iframe);
});
});
// Catbar responsive sizing
const catbar = document.querySelector('.vw-catbar');
if (catbar) {
fitCatbar(catbar);
window.addEventListener('resize', () => fitCatbar(catbar));
}
})();
Cache-Aware Rendering
Since LiteSpeed caches full HTML responses, I ensure the rendered output is cache-friendly:
class CacheAwareRenderer {
public function render(string $template, array $data, PageType $type): string {
// Set cache headers before any output
CachePolicy::apply($type, ...($data['cache_tags'] ?? []));
// Render template
$view = new View();
$html = $view->render($template, $data);
// Also write to PHP file cache as fallback
$cacheKey = md5($_SERVER['REQUEST_URI']);
$cachePath = __DIR__ . "/../data/pagecache/{$cacheKey}.html";
file_put_contents($cachePath, $html, LOCK_EX);
return $html;
}
}
Results at TopVideoHub
This SSR approach on TopVideoHub delivers:
- TTFB: ~12ms (cached), ~120ms (uncached)
- FCP: ~0.8s
- Total JS: 8KB (minified)
- Lighthouse Performance: 85+ mobile, 95+ desktop
No build step, no hydration, no JavaScript framework. Just PHP rendering HTML and LiteSpeed caching it. For content platforms, this approach is hard to beat.
Top comments (0)