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

Both pages read their content from URL query parameters:

ParamWhat it does
playlistA YouTube playlist ID (e.g. the channel's uploads playlist). Shows the latest videos from it.
maxHow many videos to show from the playlist. Defaults to 2.
videosA comma-separated list of specific YouTube URLs (or bare video IDs) to show instead of a playlist. Always overrides playlist/max when present.
titleStyleSet to regular to render titles as a bolded paragraph matching the body font, instead of the default h3 heading.

Examples

latest-videos.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=bold

A YouTube channel's "uploads" playlist ID is its channel ID with the UC prefix swapped for UU.

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; gyroscope; picture-in-picture; web-share"
  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.

How it works

Project files

index.html this page latest-videos-styled.html card layout _worker.js YouTube API proxy (Cloudflare Worker) 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.