When I first set out to aggregate my short-form content and follow key creators without falling into algorithmic rabbit holes, I ran straight into the modern web's most annoying reality: social platforms have turned into fiercely guarded walled gardens.
First, platforms killed off native RSS feeds to force everyone into apps designed for doomscrolling and ad impressions. Then, they locked down their official APIs behind tedious developer approvals, ephemeral OAuth tokens, and ridiculous enterprise paywalls (X/Twitter charging $100 to $5,000/month just for basic read access).
I didn't want to maintain heavy SDKs, register OAuth apps, or pay enterprise rates just to display my own latest videos on my website.
I wanted a simple, resilient, and open protocol: RSS (Really Simple Syndication).

Even when platforms like TikTok, Instagram, X, and Threads refuse to offer native RSS feeds, I can still generate clean, reverse-chronological RSS streams using tools like RSS-Bridge and RSS.app.
Here is how I bypass the walled gardens, take back control of my feed diet, and power the video shelf right here on this website.
1. Why I Prefer RSS Over Official APIs
Dealing with official platform APIs for simple read-only feeds is almost always overkill. Here is how I weigh the trade-offs:
| Aspect | Official Platform APIs | RSS & Bridge Feeds |
|---|---|---|
| Authentication | Developer account, OAuth2, refresh tokens | Zero auth required |
| Cost | Free tiers disappearing / exorbitant subscriptions | Free (self-hosted) or low-cost SaaS |
| Timeline Order | Algorithmic, engagement-weighted, ads | 100% Reverse-chronological |
| Privacy & Tracking | Tracked per app client ID and user session | Decoupled (my server/reader queries the bridge) |
| Interoperability | Siloed SDKs and custom JSON schemas | Universal standard (XML/Atom/JSONFeed) |
By converting everything into RSS, I can pipe all my updates into a single RSS reader (I use Miniflux and NetNewsWire) alongside tech blogs, newsletters, and release notes, without ever opening an algorithmic feed.
graph LR
subgraph Walled Gardens
TT[TikTok Profiles]
IG[Instagram Creators]
X[X / Twitter Search]
YT[YouTube Channels]
end
subgraph Feed Generators
RB[RSS-Bridge Self-Hosted / Public]
RA[RSS.app Cloud Scraper]
Native[Direct XML Endpoints]
end
subgraph My Ecosystem
NextJS[Next.js Homepage Shelf]
Reader[Miniflux / NetNewsWire]
Bot[n8n / Webhook Automations]
end
TT --> RA
IG --> RB
X --> RB
YT --> Native
RA --> NextJS
Native --> NextJS
RB --> Reader
RA --> Bot
2. Method 1: RSS-Bridge (My Go-To Open-Source Solution)
When I want full control and zero recurring costs, RSS-Bridge is my first choice. It is an open-source PHP engine that parses web pages and undocumented mobile frontend endpoints, emitting valid RSS 2.0, Atom, or JSON feeds.
It supports over 300 platforms out of the box, including TikTok, Instagram, X, YouTube, Telegram, Pinterest, and GitHub.
Quick Testing with a Public Instance
If I need to test a feed quickly without deploying anything, I use the public test instance at rss-bridge.org/bridge01/:
- Open the bridge list and select TikTok Bridge.
- Choose the query context: By User or By Hashtag.
- Input the target username (e.g.,
mrbeastor@username). - Click Atom or RSS to get the live feed URL:
https://rss-bridge.org/bridge01/?action=display&bridge=TikTokBridge&context=By+user&u=mrbeast&format=Atom
How I Self-Host RSS-Bridge with Docker
Because public instances can occasionally get rate-limited by aggressive anti-bot protections on Instagram or TikTok, I run my own private RSS-Bridge container:
docker run -d \
--name rss-bridge \
-p 3000:80 \
-v $(pwd)/config.ini.php:/app/config.ini.php:ro \
rssbridge/rss-bridge:latest
In config.ini.php, I configure aggressive caching and custom proxy routing so I never trigger platform blocks:
[Cache]
type = "fileCache"
[CustomProxy]
; Route requests through a residential or SOCKS5 proxy if needed
address = "socks5://127.0.0.1:9050"
3. Method 2: RSS.app (Turnkey Hosted Scraper)
For networks that rely heavily on client-side React/Vue rendering and aggressive Cloudflare challenges, self-hosting headless browsers can become resource-heavy. In those situations, I use RSS.app.
RSS.app runs managed headless Chromium instances in the cloud. I just paste any profile link (such as a TikTok creator URL or an Instagram account), and its engine detects the media cards, captions, and timestamps to generate an always-on XML endpoint.
When I use it: For critical integrations where I don't want to spend time maintaining scrapers or rotating residential proxies.
4. Real-World Case Study: How I Built the Video Shelf on My Homepage
Rather than just consuming these feeds in a desktop RSS reader, I use this architecture directly in the codebase of this website.
If you check the Video Chronicles shelf on my homepage, those cards are generated entirely through RSS feeds—with zero API tokens or developer subscriptions.
sequenceDiagram
participant Browser as Visitor Browser
participant Next as Next.js Server Component
participant YT as YouTube XML Feed
participant RSSApp as RSS.app (TikTok Bridge)
Browser->>Next: Load Homepage
par Query YouTube Feed
Next->>YT: GET /feeds/videos.xml?channel_id=UCofUYbRmLxAqny842JE6xsA
YT-->>Next: Atom XML Feed
and Query TikTok Feed
Next->>RSSApp: GET /feeds/nQJYzq3iyhdi3Uqz.xml
RSSApp-->>Next: RSS 2.0 XML Feed
end
Next->>Next: Parse XML, Extract Thumbnails & Sort by Date
Next-->>Browser: Render HTML Video Cards (<50ms)
Here is how my backend implementation in lib/videos.ts works:
1. Fetching My YouTube Stream
YouTube actually maintains an undocumented public Atom feed for every channel:
// lib/videos.ts
export const YOUTUBE_CHANNEL_ID = 'UCofUYbRmLxAqny842JE6xsA';
export async function fetchYouTubeRSSDirectly(limit = 3): Promise<VideoCardItem[]> {
try {
const res = await fetch(
`https://www.youtube.com/feeds/videos.xml?channel_id=${YOUTUBE_CHANNEL_ID}`,
{ cache: 'no-store' }
);
if (!res.ok) return [];
const xml = await res.text();
const entries = xml.split('<entry>').slice(1);
const videos: VideoCardItem[] = [];
for (const entry of entries) {
if (videos.length >= limit) break;
const idMatch = entry.match(/<yt:videoId>(.*?)<\/yt:videoId>/);
const titleMatch = entry.match(/<title>(.*?)<\/title>/);
const publishedMatch = entry.match(/<published>(.*?)<\/published>/);
if (idMatch && titleMatch) {
const id = idMatch[1].trim();
videos.push({
id,
title: titleMatch[1].trim(),
url: `https://www.youtube.com/watch?v=${id}`,
thumbnail: `https://i.ytimg.com/vi/${id}/hqdefault.jpg`,
platform: 'YouTube',
badge: 'YouTube · Video',
publishedAt: new Date(publishedMatch?.[1] || Date.now()).toLocaleDateString('en-US'),
});
}
}
return videos;
} catch (error) {
console.error('Error fetching YouTube RSS:', error);
return [];
}
}
2. Fetching My TikTok Stream via RSS.app
Since TikTok doesn't provide native XML, I point my server fetcher to my generated RSS.app endpoint:
// lib/videos.ts
export const TIKTOK_RSS_URL = 'https://rss.app/feeds/nQJYzq3iyhdi3Uqz.xml';
export async function fetchTikTokRSSDirectly(limit = 3): Promise<VideoCardItem[]> {
try {
const res = await fetch(TIKTOK_RSS_URL, { cache: 'no-store' });
if (!res.ok) return [];
const xml = await res.text();
const items = xml.split('<item>').slice(1);
const videos: VideoCardItem[] = [];
for (const item of items) {
if (videos.length >= limit) break;
const titleMatch = item.match(/<title><!\[CDATA\[(.*?)\]\]><\/title>/) || item.match(/<title>(.*?)<\/title>/);
const linkMatch = item.match(/<link>(.*?)<\/link>/);
const pubDateMatch = item.match(/<pubDate>(.*?)<\/pubDate>/);
const mediaContentMatch = item.match(/<media:content[^>]+url=["'](.*?)["']/i);
if (titleMatch && linkMatch) {
const url = linkMatch[1].trim();
videos.push({
id: url,
title: titleMatch[1].replace(/#\w+/g, '').trim(),
url,
thumbnail: mediaContentMatch ? mediaContentMatch[1].replace(/&/g, '&') : undefined,
platform: 'TikTok',
badge: 'TikTok · Short',
publishedAt: new Date(pubDateMatch?.[1] || Date.now()).toLocaleDateString('en-US'),
});
}
}
return videos;
} catch (error) {
console.error('Error fetching TikTok RSS:', error);
return [];
}
}
3. Merging and Sorting the Streams
On the server, I fetch both feeds in parallel, interleave them, and sort them strictly in reverse-chronological order:
export async function fetchAllVideosDirectly(): Promise<VideoCardItem[]> {
const [ytVideos, ttVideos] = await Promise.all([
fetchYouTubeRSSDirectly(3),
fetchTikTokRSSDirectly(3),
]);
return [...ytVideos, ...ttVideos]
.sort((a, b) => new Date(b.publishedAt).getTime() - new Date(a.publishedAt).getTime())
.slice(0, 3);
}
This simple pipeline gives me a fast, zero-maintenance video feed on my homepage that updates automatically whenever I post a new video or short.
Final Thoughts
You don't need expensive enterprise API keys or proprietary mobile apps to curate your digital life. By combining RSS-Bridge for self-hosted parsing and RSS.app for headless rendering, you can turn closed social media silos into clean, standardized feeds.
I’ve regained control over my attention and my site's data pipelines—and you can do the exact same.