DEV Community

ahmet gedik
ahmet gedik

Posted on

Building an RSS Feed Generator for Video Blog Content

Introduction

RSS feeds are not dead. They're essential for content distribution, SEO, and keeping engaged audiences updated. Here's how I built an RSS feed generator for the blog content on ViralVidVault that outputs valid Atom/RSS 2.0 XML.

Why RSS for a Video Platform?

Three reasons:

  1. Search engines crawl RSS feeds for content discovery
  2. Feed readers (Feedly, Inoreader) drive consistent traffic
  3. Podcatchers and aggregators can pull your video blog content automatically

The Generator

<?php

class RssFeedGenerator
{
    private string $siteUrl;
    private string $siteName;
    private string $siteDescription;

    public function __construct(
        string $siteUrl = 'https://viralvidvault.com',
        string $siteName = 'ViralVidVault',
        string $siteDescription = 'Your vault of viral videos from around the world',
    ) {
        $this->siteUrl = rtrim($siteUrl, '/');
        $this->siteName = $siteName;
        $this->siteDescription = $siteDescription;
    }

    public function generate(array $articles): string
    {
        $xml = new \DOMDocument('1.0', 'UTF-8');
        $xml->formatOutput = true;

        $rss = $xml->createElement('rss');
        $rss->setAttribute('version', '2.0');
        $rss->setAttribute('xmlns:atom', 'http://www.w3.org/2005/Atom');
        $rss->setAttribute('xmlns:media', 'http://search.yahoo.com/mrss/');
        $xml->appendChild($rss);

        $channel = $xml->createElement('channel');
        $rss->appendChild($channel);

        // Channel metadata
        $channel->appendChild($xml->createElement('title', $this->siteName));
        $channel->appendChild($xml->createElement('link', $this->siteUrl));
        $channel->appendChild($xml->createElement('description', $this->siteDescription));
        $channel->appendChild($xml->createElement('language', 'en'));
        $channel->appendChild($xml->createElement('lastBuildDate', date(DATE_RSS)));

        // Atom self-link
        $atomLink = $xml->createElement('atom:link');
        $atomLink->setAttribute('href', "{$this->siteUrl}/feed.xml");
        $atomLink->setAttribute('rel', 'self');
        $atomLink->setAttribute('type', 'application/rss+xml');
        $channel->appendChild($atomLink);

        // Items
        foreach ($articles as $article) {
            $item = $this->createItem($xml, $article);
            $channel->appendChild($item);
        }

        return $xml->saveXML();
    }

    private function createItem(\DOMDocument $xml, array $article): \DOMElement
    {
        $item = $xml->createElement('item');

        $item->appendChild($xml->createElement('title', htmlspecialchars($article['title'])));
        $item->appendChild($xml->createElement('link', "{$this->siteUrl}/blog/{$article['slug']}"));

        $guid = $xml->createElement('guid', "{$this->siteUrl}/blog/{$article['slug']}");
        $guid->setAttribute('isPermaLink', 'true');
        $item->appendChild($guid);

        $item->appendChild($xml->createElement('pubDate', date(DATE_RSS, strtotime($article['published_at']))));

        // Description with CDATA
        $desc = $xml->createElement('description');
        $desc->appendChild($xml->createCDATASection($article['excerpt'] ?? substr($article['body'], 0, 300) . '...'));
        $item->appendChild($desc);

        // Categories
        foreach ($article['tags'] ?? [] as $tag) {
            $item->appendChild($xml->createElement('category', $tag));
        }

        // Media thumbnail if available
        if (!empty($article['thumbnail'])) {
            $media = $xml->createElement('media:thumbnail');
            $media->setAttribute('url', $article['thumbnail']);
            $item->appendChild($media);
        }

        return $item;
    }
}
Enter fullscreen mode Exit fullscreen mode

Serving the Feed

<?php
// Route: /feed.xml

$db = new Database(__DIR__ . '/../data/articles.db');
$articles = $db->getRecentArticles(limit: 20);

$generator = new RssFeedGenerator();
$xml = $generator->generate($articles);

header('Content-Type: application/rss+xml; charset=utf-8');
header('Cache-Control: public, max-age=3600');
echo $xml;
Enter fullscreen mode Exit fullscreen mode

Caching the Feed

RSS feeds don't need to be generated on every request. Cache the output:

$cacheFile = __DIR__ . '/../data/pagecache/feed.xml';
$cacheTtl = 3600; // 1 hour

if (file_exists($cacheFile) && (time() - filemtime($cacheFile)) < $cacheTtl) {
    header('Content-Type: application/rss+xml; charset=utf-8');
    readfile($cacheFile);
    exit;
}

// Generate fresh feed
$xml = $generator->generate($articles);
file_put_contents($cacheFile, $xml, LOCK_EX);

header('Content-Type: application/rss+xml; charset=utf-8');
echo $xml;
Enter fullscreen mode Exit fullscreen mode

Autodiscovery

Add this to your HTML <head> so feed readers can find it:

<link rel="alternate" type="application/rss+xml" 
      title="ViralVidVault Blog Feed" 
      href="https://viralvidvault.com/feed.xml">
Enter fullscreen mode Exit fullscreen mode

Validating Your Feed

Use the W3C Feed Validation Service at https://validator.w3.org/feed/ to check your output. Common issues:

  • Missing guid elements
  • Invalid date formats (must be RFC 2822)
  • Unescaped HTML in descriptions (use CDATA)
  • Missing xmlns declarations

The RSS feed at viralvidvault.com passes W3C validation and is indexed by major feed aggregators. It's a small effort that yields steady, recurring traffic.


Part of the "Building ViralVidVault" series.

Top comments (0)