How to Manage Video Content with MDX and Supplementary Files in Next.js

The woosal1337/blog repository treats video episodes as first-class content types using a three-tier architecture: MDX pages for content and frontmatter, a dynamic metadata loader in lib/videos.ts, and a filesystem-based kit storage system for supplementary assets.

Managing video content in a static blog requires structured metadata, dynamic loading, and organized asset delivery. The woosal1337/blog repository demonstrates a complete workflow to manage video content with MDX and supplementary files by combining Next.js dynamic imports with type-safe utilities and a dedicated storage pattern for companion resources.

Architecture Overview

The system distributes video episode data across three distinct layers to separate content, metadata, and assets.

MDX Content Layer. Each episode lives in app/(website)/videos/(episode)/<slug>/page.mdx and exports a meta object conforming to VideoEpisodeFrontmatter defined in lib/video-utils.ts. This frontmatter includes the YouTube ID, title, publication date, chapters, and kit directory references.

Metadata Loader. The lib/videos.ts module exports getAllEpisodes, getEpisode, and kit file utilities that scan episode directories and dynamically import MDX modules. This approach compiles content only when requested, keeping builds fast while maintaining type safety through VideoEpisodeMeta.

Supplementary Kit Storage. Additional assets such as code samples, notebooks, and transcripts reside in videos/<slug>/kit/*. These files are served through dedicated routes and accessed via helpers like kitFileHref, listKitFiles, and readKitFile.

Defining Video Metadata Types

Type safety originates in lib/video-utils.ts, which exports the VideoEpisodeFrontmatter and VideoEpisodeMeta interfaces. These types enforce the exact shape of frontmatter exported from each MDX file.

The frontmatter structure includes:

  • title: Episode title string
  • youtubeId: YouTube video identifier
  • chapters: Array of timecodes and titles for navigation
  • kit.dir: Directory name for supplementary files
  • kit.repoUrl: Source repository reference

When an MDX file exports its metadata, the loader processes it through loadMeta(), which performs a dynamic import: import('@/app/(website)/videos/(episode)/${slug}/page.mdx').

Loading Episodes Dynamically

Episode discovery happens through filesystem operations rather than hardcoded manifests. The listSlugs() function walks app/(website)/videos/(episode) and identifies every folder containing a page.mdx file.

The public API in lib/videos.ts provides:

  • getAllEpisodes(): Returns all visible episodes, filtering out drafts and hidden entries. Returns Promise<VideoEpisodeMeta[]>.
  • getAllEpisodesWithHidden(): Includes episodes marked as hidden in the results.
  • getEpisode(slug): Loads a single episode by slug with full metadata.

This implementation enables automatic routing where adding a new folder with a page.mdx immediately makes the episode available without configuration changes.

Managing Supplementary Kit Files

Video episodes often require accompanying code samples or documentation. The kit file system stores these in videos/<slug>/kit/ and exposes them through utility functions.

Path resolution uses kitFileHref(slug, file) to generate URLs in the format /videos/<slug>/kit/<file>. File discovery uses listKitFiles(dir) to enumerate available assets, while readKitFile(dir, file) performs the actual filesystem read.

These utilities integrate with lib/video-utils.ts helpers like kitFileLanguage to determine syntax highlighting based on file extensions.

Rendering Video Components

The components/blocks/youtube-facade.tsx component handles YouTube embedding using utilities from lib/video-utils.ts. It consumes the youtubeId from episode metadata and supports chapter navigation through chapterUrl(youtubeId, startSeconds) and formatTimecode().

For episodes with chapter markers, the component generates deep links using chapterUrl(), which constructs YouTube URLs that start at specific timestamps. The chapterSeconds() helper converts timecode strings like "03:45" into seconds for the URL parameters.

Example: Listing All Episodes

To render a video archive page, consume the metadata loader API:

import { getAllEpisodes } from '@/lib/videos';

export default async function EpisodesPage() {
  const episodes = await getAllEpisodes(); // Returns VideoEpisodeMeta[]
  return (
    <ul>
      {episodes.map((e) => (
        <li key={e.slug}>
          <a href={`/videos/${e.slug}`}>{e.title}</a>
        </li>
      ))}
    </ul>
  );
}

Example: Reading Kit Files

Access supplementary content within server components using the filesystem utilities:

import { readKitFile } from '@/lib/videos';

export async function KitFileViewer({ 
  slug, 
  filename 
}: { 
  slug: string; 
  filename: string 
}) {
  const content = readKitFile(slug, `kit/${filename}`);
  return <pre className="language-python">{content}</pre>;
}

Example: Embedding with Chapter Navigation

Combine the YouTube facade with chapter utilities for rich navigation:

import { YoutubeFacade } from '@/components/blocks/youtube-facade';
import { chapterSeconds, chapterUrl, formatTimecode } from '@/lib/video-utils';

export function EpisodePlayer({ meta }: { meta: VideoEpisodeMeta }) {
  return (
    <>
      <YoutubeFacade youtubeId={meta.youtubeId} />
      {meta.chapters?.map((ch) => {
        const start = chapterSeconds(ch.time);
        return (
          <a href={chapterUrl(meta.youtubeId, start)} key={ch.time}>
            {formatTimecode(start)} – {ch.title}
          </a>
        );
      })}
    </>
  );
}

Summary

  • Video episodes in the woosal1337/blog repository are first-class content types stored as MDX files in app/(website)/videos/(episode)/<slug>/page.mdx.
  • Type safety comes from VideoEpisodeFrontmatter and VideoEpisodeMeta interfaces defined in lib/video-utils.ts, enforced at build time when the loader imports MDX modules.
  • Dynamic loading via lib/videos.ts functions like getAllEpisodes() and getEpisode() uses Next.js dynamic imports to compile MDX on demand.
  • Kit files (code samples, transcripts) reside in videos/<slug>/kit/ and are accessed through kitFileHref(), listKitFiles(), and readKitFile().
  • Rendering utilizes components/blocks/youtube-facade.tsx combined with URL helpers watchUrl(), chapterUrl(), and formatTimecode() for optimized video delivery with chapter support.

Frequently Asked Questions

How are video episode types defined in the codebase?

Type definitions reside in lib/video-utils.ts, which exports VideoEpisodeFrontmatter for MDX exports and VideoEpisodeMeta for the processed metadata object used by components. These interfaces enforce required fields like youtubeId, title, and optional chapters arrays, ensuring type safety across the ingestion and rendering pipeline.

Where should supplementary files like code samples be stored?

Supplementary assets belong in the videos/<slug>/kit/ directory parallel to the episode content. The lib/videos.ts module exposes listKitFiles() for discovery and readKitFile() for content retrieval, while kitFileHref() in lib/video-utils.ts generates canonical URLs for linking to these resources from MDX content.

How does the system handle YouTube chapter navigation?

The lib/video-utils.ts module provides chapterSeconds() to convert timestamp strings (e.g., "03:45") into seconds, and chapterUrl(youtubeId, startSeconds) to generate deep links that start playback at specific timestamps. The components/blocks/youtube-facade.tsx component consumes these utilities to render clickable chapter links alongside the video player.

What is required to add a new video episode to the blog?

Create a folder in app/(website)/videos/(episode)/<slug>/ containing a page.mdx file that exports a meta object matching VideoEpisodeFrontmatter. Optionally add a kit/ subdirectory at videos/<slug>/kit/ for supplementary files. The listSlugs() function in lib/videos.ts automatically discovers the new episode, requiring no manual route registration or build configuration changes.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →