CERG YouTube Feed

A self-hosted YouTube video feed, built to work inside an AEM instance that only allows iframe embeds.

This project is a small static site (hosted on Cloudflare Pages) that renders a YouTube video feed and is designed to be dropped into that one iframe, configured entirely through the iframe's own attributes. SFU's AEM External Video component is lacking. So, I decided to create this solution. The AEM setup only needs embedding an <iframe> and hosting one JS file.

Pages

One feed layout, with potential to create more in the future:

If no playlist or videos are configured, the iframe renders completely blank — no placeholder text.

Configuring what shows

The page reads its content from URL query parameters:

ParamWhat it does
playlistA YouTube playlist ID or playlist link (e.g. the channel's uploads playlist). Shows the latest videos from it. A channel ID (UC…) or youtube.com/channel/… link also works and shows the channel's uploads. Channel handle links (@name) don't.
maxHow many videos to show from the playlist, from 1 to 10. Defaults to 2. Private and deleted videos are skipped automatically.
videosA list of specific YouTube links (or bare video IDs) to show instead of a playlist, separated by commas, spaces or line breaks. Up to 50. Always overrides playlist/max when present.
titleStyleLeave empty for titles as a bolded paragraph matching the body font (the default). Set to heading for an h3 heading instead.

Examples

latest-videos-styled.html?playlist=UUxxxxxxxxxxxxxxxxxxxxxx&max=6
latest-videos-styled.html?videos=https://youtu.be/dQw4w9WgXcQ,https://youtu.be/xvFZjo5PgG0
latest-videos-styled.html?playlist=UUxxxx&titleStyle=heading

A YouTube channel's "uploads" playlist ID is its channel ID with the UC prefix swapped for UU. Pasting the channel ID itself does this swap automatically.

Embedding in AEM

Rather than hand-building that query string inside AEM, embeds use plain data-* attributes — a shared loader script reads them and builds the URL (with correct escaping) automatically:

<script src="https://www.sfu.ca/content/dam/sfu/politics/CERG/code/js/iframe.js"></script>
<iframe class="sfu-video-embed"
  src="https://cerg-youtube-feed.pages.dev/latest-videos-styled"
  data-videos=""
  data-playlist=""
  data-max-videos=""
  data-title-style=""
  title="CERG Youtube Feed" width="100%" height="800px" frameborder="no"
  allow="accelerometer; autoplay; clipboard-write; encrypted-media; fullscreen; gyroscope; picture-in-picture; web-share"
  allowfullscreen
  style="border-style: none; overflow: hidden; transition: height 0.2s ease;">
</iframe>

Fill in whichever data-* attribute matches what you want to show and leave the rest empty — leaving all of them empty shows nothing. The iframe also auto-resizes to fit its content height via postMessage, handled by that same script.

Keep fullscreen in allow and the allowfullscreen attribute — without them, YouTube's fullscreen button can't work inside the feed. The feed only displays when embedded on www.sfu.ca pages; other sites see a blank frame (set by frame-ancestors in _headers).

How it works

Project files

index.html this page latest-videos-styled.html card layout _worker.js YouTube API proxy (Cloudflare Worker) _routes.json sends only /api/* to the Worker _headers security headers for the static pages css/ main.css shared styles for both feed pages js/ feed.js shared data-fetching + config utilities latest-videos-styled.js page script for latest-videos-styled.html auto-resize.js reports page height to the parent AEM iframe parent-browsing-context/ iframe.js helper to build embed URLs

iframe.js — the AEM-side loader that builds each embed's URL from its data-* attributes and handles the resize listener — is hosted separately on SFU's own CDN, not in this repo.

Setting this up from scratch

  1. Get a free YouTube Data API v3 key from the Google Cloud Console (enable "YouTube Data API v3" on a project, then create an API key).
  2. Deploy this repo to Cloudflare Pages.
  3. In Pages → Settings → Variables and Secrets, add YOUTUBE_API_KEY with that key.
  4. Host iframe.js somewhere AEM can load it from, and embed the iframe snippet above wherever the feed is needed.